Page MenuHomePhorge

No OneTemporary

diff --git a/lib/Kolab/FreeBusy/SourceIMAP.php b/lib/Kolab/FreeBusy/SourceIMAP.php
index 9f39313..5a4bd43 100644
--- a/lib/Kolab/FreeBusy/SourceIMAP.php
+++ b/lib/Kolab/FreeBusy/SourceIMAP.php
@@ -1,308 +1,308 @@
<?php
/**
* This file is part of the Kolab Server Free/Busy Service
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Kolab\FreeBusy;
use Kolab\Config;
use Sabre\VObject;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\FreeBusyGenerator;
use Sabre\VObject\ParseException;
// configure env for Roundcube framework
define('RCUBE_INSTALL_PATH', KOLAB_FREEBUSY_ROOT . '/');
define('RCUBE_CONFIG_DIR', KOLAB_FREEBUSY_ROOT . '/config/');
define('RCUBE_PLUGINS_DIR', KOLAB_FREEBUSY_ROOT . '/lib/plugins/');
/**
* Implementation of a Free/Busy data source reading from IMAP
* (not yet implemented!)
*/
class SourceIMAP extends Source
{
private $folders = array();
public function __construct($config)
{
parent::__construct($config + array('mail_attributes' => 'mail'));
// load the Roundcube framework with its autoloader
require_once KOLAB_FREEBUSY_ROOT . '/lib/Roundcube/bootstrap.php';
$rcube = \rcube::get_instance(\rcube::INIT_WITH_DB | \rcube::INIT_WITH_PLUGINS);
// Load plugins
$rcube->plugins->init($rcube);
$rcube->plugins->load_plugins(array(), array('libkolab','libcalendaring'));
}
/**
* @see Source::getFreeBusyData()
*/
public function getFreeBusyData($user, $extended)
{
$log = Logger::get('imap', intval($this->config['loglevel']));
$config = $this->getUserConfig($user);
parse_str(strval($config['query']), $param);
$config += $param;
// log this...
$log->addInfo("Fetching data for ", $config);
// caching is enabled
if (!empty($config['cacheto'])) {
// check for cached data
if ($cached = $this->getCached($config)) {
$log->addInfo("Deliver cached data from " . $config['cacheto']);
return $cached;
}
// touch cache file to avoid multiple requests generating the same data
if (file_exists($config['cacheto'])) {
touch($config['cacheto']);
}
else {
file_put_contents($config['cacheto'], Utils::dummyVFreebusy($user['mail']));
$tempfile = $config['cacheto'];
}
}
// compose a list of user email addresses
$user_email = array();
foreach (Config::convert($this->config['mail_attributes'], Config::ARR) as $key) {
if (!empty($user[$key])) {
$user_email = array_merge($user_email, (array)$user[$key]);
}
}
// synchronize with IMAP and read Kolab event objects
if ($imap = $this->imap_login($config)) {
// target folder is specified in source URI
if ($config['path'] && $config['path'] != '/') {
$folders = array(\kolab_storage::get_folder(substr($config['path'], 1)));
$read_all = true;
}
else { // list all folders of type 'event'
$folders = \kolab_storage::get_folders('event', false);
$read_all = false;
}
// make \libvcalendar class available
\libcalendaring::get_ical();
$utc = new \DateTimezone('UTC');
- $dtstart = new \DateTime('now - 8 weeks 00:00:00', $utc);
- $dtend = new \DateTime('now + 16 weeks 00:00:00', $utc);
+ $dtstart = Utils::periodStartDT();
+ $dtend = Utils::periodEndDT();
$calendar = VObject\Component::create('VCALENDAR');
$seen = array();
$query = array(array('dtstart','<=',$dtend), array('dtend','>=',$dtstart));
foreach ($folders as $folder) {
$log->debug('Reading Kolab folder: ' . $folder->name, $folder->get_folder_info());
// skip other user's shared calendars
if (!$read_all && $folder->get_namespace() == 'other') {
continue;
}
// set ACL (temporarily)
if ($config['acl']) {
$folder->_old_acl = $folder->get_myrights();
$imap->set_acl($folder->name, $config['user'], $config['acl']);
}
foreach ($folder->select($query) as $event) {
//$log->debug('Processing event', $event);
if ($event['cancelled']) {
continue;
}
// only consider shared namespace events if user is a confirmed participant (or organizer)
if (!$read_all && $folder->get_namespace() == 'shared') {
$participant = false;
if (is_array($event['organizer']) && !empty($event['organizer']['email'])) {
$participant = in_array($event['organizer']['email'], $user_email);
}
else if (is_array($event['attendees'])) {
foreach ($event['attendees'] as $attendee) {
if (in_array($attendee['email'], $user_email)) {
if ($attendee['status'] == 'ACCEPTED') {
$participant = true;
break;
}
else if ($attendee['status'] == 'TENTATIVE') {
$event['free_busy'] = 'tentative';
$participant = true;
break;
}
}
}
}
if (!$participant) {
$log->debug('Skip shared event', array($event['uid'], $event['title']));
continue;
}
}
// copied from libvcalendar::_to_ical()
$ve = VObject\Component::create('VEVENT');
// translate all-day dates into absolute UTC times
// FIXME: use server timezone?
if ($event['allday']) {
$utc = new \DateTimeZone('UTC');
if (!empty($event['start'])) {
$event['start']->setTimeZone($utc);
$event['start']->setTime(0,0,0);
}
if (!empty($event['end'])) {
$event['end']->setTimeZone($utc);
$event['end']->setTime(23,59,59);
}
}
// avoid duplicate entries
$key = $event['start']->format('c') . '/' . $event['end']->format('c');
if ($seen[$key]++) {
$log->debug('Skipping duplicate event at ' . $key, array($event['uid'], $event['title']));
continue;
}
if (!empty($event['start']))
$ve->add(\libvcalendar::datetime_prop('DTSTART', $event['start'], false, false));
if (!empty($event['end']))
$ve->add(\libvcalendar::datetime_prop('DTEND', $event['end'], false, false));
if (!empty($event['free_busy']))
$ve->add('TRANSP', $event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE');
if ($event['free_busy'] == 'tentative')
$ve->add('STATUS', 'TENTATIVE');
else if (!empty($event['status']))
$ve->add('STATUS', $event['status']);
if ($event['recurrence']) {
if ($exdates = $event['recurrence']['EXDATE'])
unset($event['recurrence']['EXDATE']);
if ($rdates = $event['recurrence']['RDATE'])
unset($event['recurrence']['RDATE']);
if ($event['recurrence']['FREQ'])
$ve->add('RRULE', \libcalendaring::to_rrule($event['recurrence']));
// add EXDATEs each one per line (for Thunderbird Lightning)
if ($exdates) {
foreach ($exdates as $ex) {
if ($ex instanceof \DateTime) {
$exd = clone $event['start'];
$exd->setDate($ex->format('Y'), $ex->format('n'), $ex->format('j'));
$exd->setTimeZone($utc);
$ve->add(new VObject\Property('EXDATE', $exd->format('Ymd\\THis\\Z')));
}
}
}
// add RDATEs
if (!empty($rdates)) {
$sample = \libvcalendar::datetime_prop('RDATE', $rdates[0]);
$rdprop = new VObject\Property\MultiDateTime('RDATE', null);
$rdprop->setDateTimes($rdates, $sample->getDateType());
$ve->add($rdprop);
}
}
// append to vcalendar container
$calendar->add($ve);
}
}
$this->imap_disconnect($imap, $config, $folders);
// feed the calendar object into the free/busy generator
// we must specify a start and end date, because recurring events are expanded. nice!
$fbgen = new FreeBusyGenerator($dtstart, $dtend, $calendar);
// get the freebusy report
$freebusy = $fbgen->getResult();
$freebusy->PRODID = Utils::PRODID;
$freebusy->METHOD = 'PUBLISH';
$freebusy->VFREEBUSY->UID = date('YmdHi') . '-' . substr(md5($user_email[0]), 0, 16);
$freebusy->VFREEBUSY->ORGANIZER = 'mailto:' . $user_email[0];
// serialize to VCALENDAR format
return $freebusy->serialize();
}
// remove (temporary) cache file again
else if ($tempfile) {
unlink($tempfile);
}
return false;
}
/**
* Helper method to establish connection to the configured IMAP backend
*/
private function imap_login($config)
{
$rcube = \rcube::get_instance();
$imap = $rcube->get_storage();
$host = $config['host'];
$port = $config['port'] ?: ($config['scheme'] == 'imaps' ? 993 : 143);
$ssl = $config['scheme'] == 'imaps' || $port == 993;
// enable proxy authentication
if (!empty($config['proxy_auth'])) {
$imap->set_options(array('auth_cid' => $config['proxy_auth'], 'auth_pw' => $config['pass']));
}
// authenticate user in IMAP
if (!$imap->connect($host, $config['user'], $config['pass'], $port, $ssl)) {
Logger::get('imap')->addWarning("Failed to connect to IMAP server: " . $imap->get_error_code(), $config);
return false;
}
// fake user object to rcube framework
$rcube->set_user(new \rcube_user('0', array('username' => $config['user'])));
return $imap;
}
/**
* Cleanup and close IMAP connection
*/
private function imap_disconnect($imap, $config, $folders)
{
// reset ACL
if ($config['acl'] && !empty($folders)) {
foreach ($folders as $folder) {
$imap->set_acl($folder->name, $config['user'], $folder->_old_acl);
}
}
$imap->close();
}
}
diff --git a/lib/Kolab/FreeBusy/Utils.php b/lib/Kolab/FreeBusy/Utils.php
index 942341e..362461d 100644
--- a/lib/Kolab/FreeBusy/Utils.php
+++ b/lib/Kolab/FreeBusy/Utils.php
@@ -1,218 +1,240 @@
<?php
/**
* This file is part of the Kolab Server Free/Busy Service
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2013-2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Kolab\FreeBusy;
/**
* Static calss providing utility functions for the Free/Busy service
*/
class Utils
{
const PRODID = '-//kolab.org//NONSGML Kolab Free-Busy Service 3.2//EN';
/**
* Resolve the given directory to a real path ending with $append
*
* @param string Arbitrary directory directory path
* @param string Make path end with this string/character
* @return string Absolute file system path
*/
public static function abspath($dirname, $append = '')
{
if ($dirname[0] != '/')
$dirname = realpath(KOLAB_FREEBUSY_ROOT . '/' . $dirname);
return rtrim($dirname, '/') . $append;
}
/**
* Returns remote IP address and forwarded addresses if found
*
* @return string Remote IP address(es)
*/
public static function remoteIP()
{
$address = $_SERVER['REMOTE_ADDR'];
// use the NGINX X-Real-IP header, if set
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
$address = $_SERVER['HTTP_X_REAL_IP'];
}
// use the X-Forwarded-For header, if set
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$address = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
return $address;
}
/**
* Checks if the given IP address is in one of the provided ranges
*
* @param string IP address
* @param array List of IP ranges/subnets to check against
* @return boolean True if in range, False if not
*/
public static function checkIPRange($ip, $ranges)
{
$ipv6 = strpos($ip, ':') !== false;
$ipbin = $ipv6 ? self::ip6net2bits($ip) : ip2long($ip);
foreach ((array)$ranges as $range) {
// don't compare IPv4 and IPv6 addresses/ranges
$rangev6 = strpos($range, ':') !== false;
if ($ipv6 != $rangev6) {
continue;
}
// quick substring check (e.g. 192.168.0.)
if (( $ipv6 && strpos($ipbin, self::ip6net2bits($range)) === 0) ||
(!$ipv6 && strpos($ip, rtrim($range, '*')) === 0)) {
return true;
}
// range from-to specified (IPv4 only)
list($lower, $upper) = explode('-', $range);
if (strlen($upper) && !$ipv6) {
if ($ipbin >= ip2long(trim($lower)) && $ipbin <= ip2long(trim($upper))) {
return true;
}
}
// subnet/length is given
list($subnet, $bits) = explode('/', $range);
// IPv6 subnet
if (strlen($bits) && $ipv6) {
$subnetbin = self::ip6net2bits($subnet);
if (substr($ipbin, 0, $bits) === substr($subnetbin, 0, $bits)) {
return true;
}
}
// IPv4 subnet
else if (strlen($bits)) {
$subnet = ip2long($subnet);
$mask = -1 << $bits;
$subnet &= $mask; // just in case the supplied subnet wasn't correctly aligned
if (($ipbin & $mask) == $subnet) {
return true;
}
}
}
return false;
}
/**
* Convert the given IPv6 address to a binary string representation.
* (from http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet)
*/
public static function ip6net2bits($inet)
{
$binaryip = '';
$unpacked = @unpack('A16', inet_pton($inet));
foreach (str_split($unpacked[1]) as $char) {
$binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
return $binaryip;
}
/**
* Returns number of seconds for a specified offset string.
*
* @param string String representation of the offset (e.g. 20min, 5h, 2days, 1week)
* @return int Number of seconds
*/
public static function getOffsetSec($str)
{
if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) {
$amount = (int) $regs[1];
$unit = strtolower($regs[2]);
}
else {
$amount = (int) $str;
$unit = 's';
}
switch ($unit) {
case 'w':
$amount *= 7;
case 'd':
$amount *= 24;
case 'h':
$amount *= 60;
case 'm':
$amount *= 60;
}
return $amount;
}
/**
* Getter for the free/busy period start time
*
* @return int Unix timestamp
*/
public static function periodStart()
{
// Should probably be a setting. For now, do 8 weeks in the past
return time() - (60 * 60 * 24 * 7 * 8);
}
+ /**
+ * Getter for the free/busy period start time
+ *
+ * @return object DateTime instance
+ */
+ public static function periodStartDT()
+ {
+ // Should probably be a setting. For now, do 8 weeks in the past
+ return new \DateTime('now - 8 weeks 00:00:00', new \DateTimezone('UTC'));
+ }
+
/**
* Getter for the free/busy period end time
*
* @return int Unix timestamp
*/
public static function periodEnd()
{
// Should probably be a setting. For now, do 16 weeks into the future
return time() + (60 * 60 * 24 * 7 * 16);
}
+ /**
+ * Getter for the free/busy period end time
+ *
+ * @return object DateTime instance
+ */
+ public static function periodEndDT()
+ {
+ // Should probably be a setting. For now, do 8 weeks in the past
+ return new \DateTime('now + 16 weeks 00:00:00', new \DateTimezone('UTC'));
+ }
+
/**
* Returns an apparent empty Free/Busy list for the given user
*/
public static function dummyVFreebusy($user)
{
$now = time();
$dtformat = 'Ymd\THis\Z';
$dummy = "BEGIN:VCALENDAR\n";
$dummy .= "VERSION:2.0\n";
$dummy .= "PRODID:" . self::PRODID . "\n";
$dummy .= "METHOD:PUBLISH\n";
$dummy .= "BEGIN:VFREEBUSY\n";
$dummy .= "ORGANIZER:MAILTO:" . $user . "\n";
$dummy .= "DTSTAMP:" . gmdate($dtformat) . "\n";
$dummy .= "DTSTART:" . gmdate($dtformat, self::periodStart()) . "\n";
$dummy .= "DTEND:" . gmdate($dtformat, self::periodEnd()) . "\n";
$dummy .= "COMMENT:This is a dummy vfreebusy that indicates an empty calendar\n";
$dummy .= "FREEBUSY:19700101T000000Z/19700101T000000Z\n";
$dummy .= "END:VFREEBUSY\n";
$dummy .= "END:VCALENDAR\n";
return $dummy;
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 22, 11:22 AM (1 d, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
80103
Default Alt Text
(16 KB)

Event Timeline