Ankündigung

Einklappen
Keine Ankündigung bisher.

nextCloud (ownCloud) Kalender per CalDAV einbinden

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    nextCloud (ownCloud) Kalender per CalDAV einbinden

    Mit dem angehängten Modul auf der Basis des CalDAV-Moduls von Johannes Willnecker lässt sich ein NextCloud- bzw. OwnCloud-Kalender einbinden.

    Dazu ist nötig:

    der Servername (http oder https)
    Username und Passwort für den Server (falls https mit Anmeldung eingerichtet ist) - <user> / <pass>
    Username und Passwort für NextCloud (bei mir identisch mit Serveranmeldung. Unterschiedliche Logins für HTTPS und NextCloud werden derzeit noch nicht unterstützt.
    Namen des Kalenders bzw Liste der Kalender (mit Komma getrennt) <calendar>

    Da über die Smartvisu-Konfiguration nicht sinnvoll alle Kombinationen abgedeckt werden können, muss die URL im Konfigurationsfeld Kalender die folgenden Elemente enthalten:

    Code:
    http(s)://<user>:<pass>@<server>/remote.php/dav/calendars/<caluser>/
    Falls eine ältere Version von OwnCloud im Einsatz ist, muss die URL ggf. noch angepasst werden. In der Kalender-App kann der Link für den jeweiligen Kalender abgerufen werden.

    Wenn man dann im Beschreibungsfeld des Kalenders die Daten wie folgt eingibt
    Code:
    @ icon        icons/ws/message_garbage.svg
    @ color       #cccc00
    (hinter das @ gehört kein Leerzeichen!) dann zeigt er das Mülltonnensymbol in der entsprechenden Farbe an.

    lib/calendar/service/nextcloud.php:
    Code:
    <?php
    /**
     * -----------------------------------------------------------------------------
     * @package     smartVISU
     * @author      Johannes Willnecker, Sebastian Helms
     * @copyright   2015
     * @license     GPL [http://www.gnu.de]
     * -----------------------------------------------------------------------------
     */
    
    
    require_once '../../../lib/includes.php';
    require_once const_path_system.'calendar/calendar.php';
    
    /**
     * This class reads a caldav calendar
     */
    class calendar_caldav extends calendar
    {
    
        private function startElement($element)
        {
            if(strcmp($element,"VEVENT") == 0)
            {
                $this->vevent = array(
                    'start' => date('y-m-d', 0).' '.gmdate('H:i:s', 0),
                    'end' => date('y-m-d', 0).' '.gmdate('H:i:s', 0),
                    'title' => (string)(""),
                    'content' => (string)(""),
                    'where' => (string)(""),
                    'link' => (string)("")
                );
                $this->startts = 0;
                $this->inVEVENT = true;
            }
        }
    
        private function endElement($element)
        {
            if($element == 'VEVENT')
            {
                $this->startdatearray[] = $this->startts;
                $this->data[] = $this->vevent;
                $this->inVEVENT = false;
            }
        }
    
        private function Value($name, $value)
        {
            if($this->inVEVENT == true)
            {
                if($name == 'SUMMARY')
                {
                    $this->vevent['title'] = (string)($value);
                }
                if($name == 'DESCRIPTION')
                {
                    $this->vevent['content'] = (string)($value);
                    foreach (explode("\\n", $value) as $line)
                    {
                        preg_match_all('/@([^ ]+) +(.*)$/', $line, $items);
                        if ($items[1][0] == "icon" or $items[1][0] == "color")
                        {
                            $this->vevent[$items[1][0]] = (string)$items[2][0];
                        }
                    }
                }
                if($name == 'LOCATION')
                {
                    $this->vevent['where'] = (string)($value);
                }
                if($name == 'DTSTART')
                {
                    preg_match('/((.*)=(.*):)?(.*)/', $value, $matches);
                    //TODO TZID handling
                    date_default_timezone_set('Europe/Berlin');
                    $ts = strtotime($matches[4]) + date("Z", strtotime($matches[4]));
                    $this->startts = $ts;
                    $this->vevent['start'] = date('y-m-d', $ts).' '.gmdate('H:i:s', $ts);
                }
                if($name == 'DTEND')
                {
                    preg_match('/((.*)=(.*):)?(.*)/', $value, $matches);
                    //TODO TZID handling
                    date_default_timezone_set('Europe/Berlin');
                    $ts = strtotime($matches[4]) + date("Z", strtotime($matches[4]));
                    $this->vevent['end'] = date('y-m-d', $ts).' '.gmdate('H:i:s', $ts);
                }
            }
        }
    
    
        private function get_caldav_calendar($url)
        {
    
            $calStart = gmdate("Ymd\THis\Z");
            $calEnd = gmdate("Ymd\THis\Z", strtotime("+4 weeks"));
    
            $postdata = "<C:calendar-query xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">
     <D:prop>
       <C:calendar-data>
         <C:expand start=\"".$calStart."\"
                   end=\"".$calEnd."\"/>
       </C:calendar-data>
     </D:prop>
     <C:filter>
       <C:comp-filter name=\"VCALENDAR\">
         <C:comp-filter name=\"VEVENT\">
           <C:time-range start=\"".$calStart."\"
                         end=\"".$calEnd."\"/>
         </C:comp-filter>
       </C:comp-filter>
     </C:filter>
    </C:calendar-query>";
    
            $ctxopts =     array('http' =>
                            array(    'method' => 'REPORT',
                                    'header' => "Depth: 1\r\n",
                                                "Content-Type: application/xml\r\n",
                                    'content' => $postdata
                            )
                        );
            $context = stream_context_create($ctxopts);
    
            $content = file_get_contents($url, false, $context);
    
            return $content;
        }
    
        public function run()
        {
    
            $srcurl = $this->url;
    
            if (strpos(config_calendar_name, ",") > 0)
            {
                $snippets = explode(",", config_calendar_name);
            }
            else
            {
                $snippets[] = config_calendar_name;
            }
    
            if (substr($srcurl,-1) !== "/" and $snippets[0] !== "")
            {
                $srcurl .= "/";
            }
    
            foreach ($snippets as $snippet)
            {
                $urls[] = $srcurl . $snippet;
            }
    
            foreach ($urls as $url)
            {
                $content = $this->get_caldav_calendar($url);
                $this->debug($content);
    
                if ($content !== false)
                {
                    $xmls[] = simplexml_load_string($content);
                }
            }
    
            foreach($xmls as $xml)
            {
                if ($xml !== false)
                {
    
                    $this->i = 1;
                    foreach ($xml->children('d', true) as $entry)
                    {
                        foreach ($entry->propstat->prop->children('cal',true) as $cal)
                        {
                            if($cal->getName() == 'calendar-data')
                            {
                                $cal = str_replace (array("\r\n ", "\n ", "\r "), '', $cal);
                                preg_match_all('/(.[^;|:]*)?(;|:)(.*)/', $cal, $matches, PREG_SET_ORDER);
    
                                foreach($matches as $values)
                                {
                                    if($values[1] == 'BEGIN')
                                    {
                                        $this->startElement(trim($values[3]));
                                    }
                                    else if($values[1] == 'END')
                                    {
                                        $this->endElement(trim($values[3]));
                                    }
                                    else
                                    {
                                        $this->Value($values[1], trim($values[3]));
                                    }
                                }
                            }
                        }
                    }
                }        
                else
                {
                    $this->error('Calendar: caldav', 'caldav: Calendar read request failed!');
                }
            }
    
            //order events:
            array_multisort($this->startdatearray, SORT_ASC, $this->data);
            $i = 1;
            foreach($this->data as $key => $value)
            {
                $this->data[$key]['pos'] = $i++;
            }
    
            $this->data = array_slice($this->data, 0, $this->count);
        }
    }
    
    
    // -----------------------------------------------------------------------------
    // call the service
    // -----------------------------------------------------------------------------
    
    $service = new calendar_caldav(array_merge($_GET, $_POST));
    echo $service->json();
    
    ?>


    Danke für die Fehlerbehebungen an offline und gnarrf!

    Zuletzt geändert von Morg; 02.02.2017, 22:20. Grund: Fehlerbehebungen im Code

    #2
    Das ist super, dass Du das in Angriff genommen hast. Soetwas fehle mir noch aber ich bekomme es leider auch noch nicht ans Laufen. Ich habe eine eigene ownCloud bei mir auf dem Server laufen und versuche den CALDAV Link anzusprechen. Der folgende Link wird mir von OwnCloud angegeben und dieser funktioniert auch mit dem Handy und dem PC
    http://<user>:<password>@<ipadresse>/remote.php/dav/calendars/admin/christoph

    Einstellungen auf der Config Seite
    http://<user>:<password>@<ipadresse>/remote.php/dav/calendars/admin/christoph
    Username:
    Password:
    Calendername:
    --> Error: caldav: Calendar read request failed!

    http://<user>:<password>@<ipadresse>/remote.php/dav/calendars/admin/christoph
    Username: admin
    Password: password
    Calendername: christoph
    --> Error: caldav: Calendar read request failed!

    http://<user>:<password>@<ipadresse>/remote.php/dav/calendars/admin/
    Username: admin
    Password: password
    Calendername: christoph
    --> Kein Error aber auch kein Ergebnis

    http://<user>:<password>@<ipadresse>/remote.php/dav/calendars/admin
    Username: admin
    Password: password
    Calendername: christoph
    --> Kein Error aber auch kein Ergebnis

    http://<user>:<password>@<ipadresse>/remote.php/dav/calendars/
    Username: admin
    Password: password
    Calendername: christoph
    --> Kein Error aber auch kein Ergebnis

    Habt ihr noch Ideen, wie ich den Fehler weiter eingrenzen kann? Habe auch mal die Variable $content versicht auszugeben. Diese war leider leer.

    Gruß
    Christoph
    Zuletzt geändert von loeserman; 02.10.2016, 17:37.

    Kommentar


      #3
      Ja, einen hab ich noch.

      Es gibt wohl eine Inkompatibilität zwischen sabre-dav und (zumindest) dem iPhone-CalDAV-Client. Ich bin mir nicht sicher, ob ggf. auch der SV-Client damit Probleme hat...

      Wenn es dein eigener Server ist, dann schau dir mal die Datei >> 3rdparty/sabre/dav/lib/CalDAV/Plugin.php << im Owncloud-Web-Ordner an.

      Da solltest du eine Stelle finden (bei meiner Version Zeile 581):
      Code:
                  if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
                      // Microsoft clients incorrectly supplied depth as 0, when it actually
                      // should have set depth to 1. We're implementing a workaround here
                      // to deal with this.
                      //
                      // This targets at least the following clients:
                      //   Windows 10
                      //   Windows Phone 8, 10
                      $depth = 1;
                  } else {
                      throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
                  }
      Offensichtlich wird hier die falsche Depth abgefragt. Da ich zu faul war, hier einen User-Agent-abhängigen Workaround zu basteln und ich der Einzige bin, der den Kalender benutzt, habe ich das anders gelöst:

      Code:
              $depth = 1;
                  if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
                      // Microsoft clients incorrectly supplied depth as 0, when it actually
                      // should have set depth to 1. We're implementing a workaround here
                      // to deal with this.
                      //
                      // This targets at least the following clients:
                      //   Windows 10
                      //   Windows Phone 8, 10
                      $depth = 1;
                  } else {
                  //     throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
                  }
      Ist krude, funktioniert aber zum Testen (und bei mir für den "Betrieb"... )

      Wenn's das nicht ist, dann versuche ich gern zu debuggen, dann müsste ich aber deine Zugangsdaten habe (ggf. mit vorübergehend geändertem Passwort oder so)

      Kommentar


        #4
        Jau das hat hingehauen. Vielen Dank schon mal für die superschnelle Reaktion. Allerdings weiß ich nicht, ob das vielleicht nun auch meine anderen Applikationen (z.B. Sync der Handys bzw. der PCs) betrifft. Hat das Einfluss darauf?

        Kann ich auch mehrere Kalender auslesen? Der Benutzer ist bei mir immer der gleiche. Habe versucht das Widget anzupassen und die URL zu übergeben. Da tut sich dann auch etwas aber ich bekomme nur die Ergebnisse des letzten Kalenders angezeigt. Diese aber dafür zweimal. Naja da muss ich mal noch suchen.

        Kommentar


          #5
          Mehrere Kalender müsste gehen, habe ich noch nicht probiert. Ist meine nächste Aufgabe

          Auf "normale" Kalenderabfragen sollte es keine Auswirkungen haben. Notfalls musst du mal mitplotten, mit welchem User-Agent-String der caldav-Client sich verbindet und die Ausnahme, die im Original für Microsoft drin ist, entsprechend anpassen.

          Du könntest auch die Zeile "$depth = 1", die ich oberhalb eingefügt habe, an die Stelle der auskommentierten Zeile stellen - dann würde das nur noch greifen, wenn ansonsten ein Fehler auftreten würde - also nicht im normalen Betrieb. Wäre wahrscheinlich die sauberere Lösung...

          Kommentar


            #6
            Ah das ist super, dass Du das noch ausprobieren willst. Bitte halte mich auf dem Laufenden, wie mehrere Kalender angegeben werden können.

            Was das Thema mit dem "$depth = 1" angeht, so habe ich mal versucht herauszufinden, als welcher User-Agent sich denn die SMartVisu anmeldet. Hierzu habe ich einfach vor dem "if" eine Exception geworfen. Allederings musste ich feststellen, dass der User-Agent String leer ist.

            Code:
               throw new \Exception( $this->server->httpRequest->getHeader('User-Agent') );
               if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
                  ...
            Ergebnis: Exception (zu sehen im OwnCLoud Log im WebInterface):
            {"Message":"","Exception":"Exception","Code":0,"Tr ace":"#0 ... ,"File":"\/opt\/bitnami\/apps\/owncloud\/htdocs\/3rdparty\/sabre\/dav\/lib\/CalDAV\/Plugin.php","Line":588,"User":"admin"}

            Daher werde ich Deinem Rat folgen und im Fehlerfall $depth auf 1 setzen.

            Code:
               if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
                  // Microsoft clients incorrectly supplied depth as 0, when it actually
                  // should have set depth to 1. We're implementing a workaround here
                  // to deal with this.
                  //
                  // This targets at least the following clients:
                  //   Windows 10
                  //   Windows Phone 8, 10
                  $depth = 1;
               } else {
                  // throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
                  // ## LÖSER MODIFIKATION: Setze im Fehlerfall die Tiefe auf 1, damit die SmartVisu korrekt zugreifen kann.
                  $depth = 1;
               }
            Vielen Dank schon mal. Nun schauen wir mal, ob das mit den mehreren Kalendern auch noch klappt.

            Kommentar


              #7
              Ich habe heute morgen mal sowohl den caldav-Client als auch den iCal-Client durchgeforstet.

              Es wäre eine schöne Lösung, das Parsing aus dem caldav-Client zu entfernen und durch den iCal-Parser ersetzen zu lassen.

              Im Moment sehe ich (für mich) noch zwei Probleme:

              a) ich finde noch nicht, wie die Argumente user/pass/calendar aus den SV-Einstellungen übergeben werden. Ich wollte unter Kalender zB "abfall,geburtstage" auslesen und nach Kommata getrennt die ganze caldav-Geschichte mehrfach aufrufen - allein der Mehrfachaufruf dürfte noch spannend werden, wenn es darum geht, die Ergebnisse zu komibieren

              b) CalDav liefert nicht eine ics-Datei zurück (dann wäre das iCal-Parsen einfach), sondern eine XML-Datei mit vielen Dateien (wenn ich das richtig interpretiere, eine pro Tag).
              Das "Schleifenparsen" in caldav funktioniert dabei - wenn ich das mit iCal machen möchte, muss ich wohl erst alle VCALENDAR-Rahmen zusammenfassen und alle VEVENT-Ereignisse in einem Rutsch übergeben.

              Hatte ich heute noch keine Lust zu

              Kommentar


                #8
                So, ich habe mich nochmal rangesetzt.

                Da ich immer noch nicht rausbekommen habe, wie (ob!) die Argumente für Username, Passwort, Kalendername übergeben werden (im Parameterarray finde ich sie zur Laufzeit nicht wieder, siehe widgets/calendar.php!), habe ich mir eine neue Syntax ausgedacht.

                Der URL-String lautet dann zB

                https:// user : pass@server.tld/remote.php/dav/calendars/user/{calendar1,calendar2,calendar3}/

                In dem Fall werden der Reihe nach die Kalender

                https:// user : pass@server.tld/remote.php/dav/calendars/user/calendar1/
                https:// user : pass@server.tld/remote.php/dav/calendars/user/calendar2/
                https:// user : pass@server.tld/remote.php/dav/calendars/user/calendar3/


                abgefragt und als ein Gesamtkalender dargestellt (zeitlich entsprechend sortiert).

                Die geschweiften Klammern zeigen die "Mehrfachauswahl" an, die Argumente müssen mit Komma getrennt sein. Wenn keine Klammern da sind, verhält sich das Plugin wie bisher und ruft nur den angegebenen Kalender ab.

                EDIT: Anzahl der anzuzeigenden Kalenderelemente wird jetzt berücksichtigt.

                Code siehe 1. Posting
                Zuletzt geändert von Morg; 03.10.2016, 21:31. Grund: Code im 1. Posting

                Kommentar


                  #9
                  Cool. Funktioniert.
                  Bis auf die Länge, aber das schreibst Du ja selber schon.
                  Danke nochmal für Deine vielen Mühen.

                  Kommentar


                    #10
                    Zitat von Morg Beitrag anzeigen
                    Da ich immer noch nicht rausbekommen habe, wie (ob!) die Argumente für Username, Passwort, Kalendername übergeben werden (im Parameterarray finde ich sie zur Laufzeit nicht wieder, siehe widgets/calendar.php!), habe ich mir eine neue Syntax ausgedacht.
                    Die werden nicht übergeben, sondern du kannst sie serverseitig aus der Konfiguration lesen. Die config.php wird in der /lib/includes.php included.
                    Dort werden folgende PHP-Konstanten definiert, wenn diese auf der Konfigurationsseite eingegeben wurden:
                    • config_calendar_url
                    • config_calendar_username
                    • config_calendar_password
                    • config_calendar_name
                    Keine Ahnung, weshalb die URL per Ajax übergeben wird, dies wäre ja auch nicht notwendig.

                    Kommentar


                      #11
                      Ah, danke.. dann ändere ich das noch.
                      @loeserman: Die Länge = Anzahl der Items habe ich schon gefixt

                      Kommentar


                        #12
                        Die URL für den Kalender muss angepasst werden, der Kalendername darf jetzt nicht mehr drin stehen. Der/die gewünschten Kalender werden mit Komma getrennt im Config-Feld Kalendername angegeben.

                        Code steht (jetzt) nur noch im 1. Posting

                        Kommentar


                          #13
                          Super Arbeit Morg. Vielen Dank!

                          Kommentar


                            #14
                            Ganz vielen Dank für Deine Arbeit Morg, funzt prima lokal mit OwnCloud 8.1 auf meiner QNAP 251+! Die smartVisu 2.8 läuft seit gestern ebenfalls auf dem Standard-Webserver der QNAP. Google Kalender etc. hat sich somit für mich erledigt. Da es etwas Probiererei und Gefummel war, hier nochmal die notwendigen Schritte für den geneigten Mitleser:

                            1. lib/calendar/service/nextcloud.php anlegen, siehe Post #1.
                            2. config.php im smartVisu-Verzeichnis editieren. Beispiel:

                            Code:
                             define('config_calendar_service', 'nextcloud');
                             define('config_calendar_url', 'http://tom:meingeheimespasswort@192.168.178.52/owncloud/remote.php/caldav/calendars/tom/');
                             define('config_calendar_username', 'tom');
                             define('config_calendar_password', 'meingeheimespasswort');
                             define('config_calendar_name', 'kalender1,kalender2,müll');
                            Sonstige Bemerkungen:
                            • Für die Standardversion von OwnCloud auf QNAP-NAS (8.1) musste ich .../remote.php/dav... auf .../remote.php/caldav... anpassen.
                            • Es scheint 1-2 Minuten zu dauern, bis die Visu die cfg-Änderung mitbekommt (oder der Kalenden neu abgeglichen wird?) - ggf. mehrfach Ctrl-F5 drücken und etwas Geduld zeigen.
                            • Die genannten Kalender wurden von mir händisch angelegt. Gross-/Kleinschreibung scheint keine Rolle zu spielen.

                            Danke nochmal, ich fang jetzt mal wieder an, mit meinem Helios-Plugin/Widget weiterzumachen, der Winter naht.

                            /tom

                            Edit/Nachtrag (der Vollständigkeit halber): Bei der Standardinstallation auf dem NAS wird OC8.1 ohne Kalender und Kontakte installiert. Die notwendigen Pakete zum Nachinstallieren findet man hier und hier. Bitte unbedingt die Download-Versionen für 8.0 auswählen - die für 8.1 lassen sich komischerweise nicht installieren, Abbruch mit Fehlermeldung "Es muss mindestens OC 8.1 installiert sein" (was ja eigentlich der Fall ist). Also: 8.0 nehmen, die gehen problemlos. /tom
                            Zuletzt geändert von Tom Bombadil; 07.10.2016, 10:34.

                            Kommentar


                              #15
                              freut mich, wenn's auch anderen hilft!

                              Ich nutze (mittlerweile) NextCloud, da sind die Pfade etwas unterschiedlich. Der Kalender-Reload (hatte viel mit ERROR:.. zu kämpfen ) ging bei mir ziemlich flott, da gab es eigentlich keine Verzögerungen. Allerdings läuft bei mir das Kalenderskript relativ lange...

                              Die Kalender- und Kontakte-Apps in OwnCloud kannst du als Admin doch direkt aus der Oberfläche heraus installieren... oder ging das auch nicht?

                              Kommentar

                              Lädt...
                              X