Added migration script for Oracle Calendar. Updates the guides for the 1.3.1 release and the NEWS file.

Monotone-Parent: 618ad1eb6b0c707afbfd6673022c7840209747e1
Monotone-Revision: b59f331214320317bf10c70f2db10187fcd5f29d

Monotone-Author: ludovic@Sophos.ca
Monotone-Date: 2010-08-19T13:48:53
maint-2.0.2
Ludovic Marcotte 2010-08-19 13:48:53 +00:00
parent 4e22e4fd67
commit ec9fcce3c6
9 changed files with 938 additions and 5 deletions

View File

@ -0,0 +1,28 @@
Depending on your location, the timezone will have to be adjusted
in the oracleimport script. Search for "$timezone" and adjust it together
with the DTSTART/DTEND values.
Also, if you do not want the script to send invitation requests / updates
for virtually every PUT operation, you MUST add the SCHEDULE-AGENT parameter
(with a value of NONE or CLIENT) to the ORGANIZER of the events you're about
to import. See http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-08#section-10.1
for all details.
Then, you need to export the data from Oracle Calendar. To do this, use
the following tools based on the type of data you want to export.
- Calendars -
% uniical -export -host <hostname:port> -n <nodeid> -start <month>/<day>/<year> -end <month>/<day>/<year> -u <username> -f <filename>
- Tasks -
% unicpoutu -host <hostname:port> -n <nodeid> -start <day> <month> <year> -end <day> <month> <year> -u <username>
- Rights -
% uniaccessrights -ls -grantor <user> -host <hostname:port> -n <nodeid> -p XXXX -grantee "S=*" -designate
Please look at http://download.oracle.com/docs/cd/B25553_01/calendar.1012/b25486/utilities.htm for
details on those tools.

View File

@ -0,0 +1,887 @@
#!/usr/bin/perl
use strict;
use warnings;
use Time::Local;
use Time::localtime;
use Getopt::Long qw(:config bundling);
use HTTP::Request;
use LWP::UserAgent;
use Math::BigInt;
use Digest::MD5 qw(md5_hex);
use MIME::QuotedPrint;
use MIME::Base64;
use Net::LDAP;
use Time::HiRes qw (gettimeofday tv_interval);
use constant {
QUOTED_PRINTABLE => 'auto',
# 0 : never decode data
# 1 : always decode data
# auto : try to guess from the file headers
OWNER => undef,
# undef : use event organizer as the owner
# <username> : force owner to be username
DUPLICATES => 'update',
# create : create a new entry if the UID already exists
# ignore : don't create the event if the UID already exists
# update : update the entry with the same UID (if deleted, stays deleted)
# replace : if the entry exists and was deleted, resurect it
RECURRENT => 0,
# smart : group new events with same UID as one recurrent event
LDAP_HOST => 'ldap://ldap.foobar.edu',
LDAP_BIND_DN => 'uid=sogo,ou=applications,dc=foobar,dc=edu',
LDAP_BIND_PW => 'PASSWORD',
LDAP_BASE => 'ou=people,dc=foobar,dc=edu',
LDAP_USERNAME => 'uid',
LDAP_EMAIL => 'mail',
LDAP_EMAIL_FILTER => '(|(mail=%s)(mailAlternateAddress=%s))',
FORCE_USERNAME => undef,
FORCE_CLOSE => 0,
DRYRUN => 0,
DEBUG => 1
};
$| = 1;
# Global variables
my $file;
my ($username, $email);
my $url;
my ($host, $port, $authusername, $password);
my $ua;
my $ldap;
my %duplicatedUID = ();
my $pwdhash;
my $timezone = <<_EOF;
BEGIN:VTIMEZONE
TZID:/inverse.ca/20091015_1/America/New_York
X-LIC-LOCATION:America/New_York
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
_EOF
sub usage
{
my $msg = shift;
print "$msg\n" if ($msg);
print "Usage: $0 <url> <username> <type> <filename>\n";
print " <url> must have the form http[s]://[superuser]:[password]\@hostname\n";
print " The full URL will be build using the username.\n";
print " <type> must be 'events', 'tasks' or 'rights' to specify the type of data\n";
print " <filename> must have the form <type>.<username>\n";
print "\n";
}
sub getEmailByUsername
{
my ($ldap, $username) = @_;
my $results = $ldap->search(base => LDAP_BASE,
filter => '('.LDAP_USERNAME.'='.$username.')',
attrs => [(LDAP_EMAIL)]);
if ($results->count != 1) {
print "Unexpected number of LDAP entries (",$results->count,") for $username\n";
return 0;
}
my $entry = $results->entry(0);
return $entry->get_value(LDAP_EMAIL);
}
my %emailToUserName;
sub getUsernameByEmail
{
my ($ldap, $email) = @_;
if (!defined($emailToUserName{$email})) {
my $results = $ldap->search(base => LDAP_BASE,
filter => sprintf(LDAP_EMAIL_FILTER, $email, $email),
attrs => [(LDAP_USERNAME)]);
if ($results->count != 1) {
print "Unexpected number of entries return for $email\n";
return 0;
}
my $entry = $results->entry(0);
$emailToUserName{$email} = $entry->get_value(LDAP_USERNAME);
}
return $emailToUserName{$email};
}
sub calendarUrl
{
my $username = $_[0];
my $uid = $_[1] || "";
return "$url/SOGo/dav/$username/Calendar/personal/$uid";
}
sub httpRequest
{
my ($request, $uid) = @_;
my $result = 1;
my $i;
for ($i = 0; $i < 30; $i++) {
my $response = $ua->request($request);
if ($response->is_success) {
print $request->method, " $uid:\t", $response->status_line, "\n";
last;
}
else {
print STDERR "ERR ", $request->method, " $uid:\t", $response->status_line, "\n";
if ($response->code == 500) {
print STDERR "INFO sleeping 2 secs\n";
sleep(2);
}
else {
$result = 0;
last;
}
}
}
if ($i == 30) {
print STDERR "ERR ", $request->method, " $uid:\tCan't reach server for the past 60 secs - exiting.\n";
exit(-4);
}
return $result;
}
sub userCalendarExists
{
my ($username) = @_;
my $result = 0;
my $propfind = '<?xml version="1.0" encoding="utf-8"?><D:propfind xmlns:D="DAV:"><D:allprop/></D:propfind>';
my $request = HTTP::Request->new();
$request->method('PROPFIND');
$request->uri(&calendarUrl($username));
$request->header('Content-Type' => 'text/xml; charset=utf8');
$request->header('Content-Length' => length($propfind));
$request->header('Depth' => 0);
$request->header('Authorization' => "Basic $pwdhash");
$request->content($propfind);
$result = &httpRequest($request, $username);
return $result;
}
sub searchByUid
{
my ($username, $uid) = @_;
my $result = 0;
my $request = HTTP::Request->new();
$request->method('GET');
$request->uri(&calendarUrl($username, $uid));
$result = &httpRequest($request, $uid);
return $result;
}
sub deleteEvent()
{
my ($username, $uid) = @_;
my $result = 0;
return $result if (DRYRUN);
my $request = HTTP::Request->new();
$request->method('DELETE');
$request->uri(&calendarUrl($username, $uid));
$result = &httpRequest($request, $uid);
return $result;
}
sub putEvent(\%)
{
my (%vevent) = %{(shift)};
my $count = shift;
my $uid = $vevent{'uid'};
# decode data
$vevent{'data'} =~ s/\r//g;
$vevent{'data'} =~ s/([^=])\n /$1/g;
if (QUOTED_PRINTABLE eq '1' ||
(QUOTED_PRINTABLE eq 'auto' && $vevent{'encoding'} && $vevent{'encoding'} =~ m/quoted-printable/)) {
$vevent{'data'} = decode_qp($vevent{'data'});
}
# for "notes", we need to add one day to the DTEND
my $oracleEventType;
if ($vevent{'data'} =~ /X-ORACLE-EVENTTYPE:(.*)/) {
$oracleEventType = $1;
} else {
$oracleEventType = "unknown";
}
if ($oracleEventType eq 'DAILY NOTE') {
# if ($vevent{'data'} =~ /DTEND;VALUE=DATE:(\d{4})(\d{2})(\d{2})/) {
# my ($mday,$mon,$year) = ($3, $2, $1);
# my $seconds = timelocal(0, 0, 0, $mday, $mon - 1, $year - 1900);
# $seconds += 86400;
# # we specify "CORE::" because we expect an array instead of a
# # magical hash
# my @newLocalTime = CORE::localtime($seconds);
# $mday = $newLocalTime[3];
# $mon = $newLocalTime[4] + 1;
# $year = $newLocalTime[5] + 1900;
# my $newEndDate = sprintf("%.4d%.2d%.2d", $year, $mon, $mday);
# my $dtEndPrefix = "DTEND;VALUE=DATE:";
# my $dtEndIndex = index $vevent{'data'}, $dtEndPrefix;
# if ($dtEndIndex > -1) {
# my $partLength = $dtEndIndex + length($dtEndPrefix);
# $vevent{'data'} = sprintf("%s%s%s",
# substr($vevent{'data'}, 0, $partLength),
# $newEndDate,
# substr($vevent{'data'}, $partLength + 8));
# }
# }
# we set a timezone for dates in all day events to ensure that SOGo
# does not put them in UTC
$vevent{'data'} =~ s@BEGIN:VEVENT@${timezone}BEGIN:VEVENT@;
$vevent{'data'} =~ s@DTSTART;VALUE=DATE:@DTSTART;VALUE=DATE;TZID=/inverse.ca/20091015_1/America/New_York:@;
$vevent{'data'} =~ s@DTEND;VALUE=DATE:@DTEND;VALUE=DATE;TZID=/inverse.ca/20091015_1/America/New_York:@;
}
# parse attendees
my $hasAttendees = 0;
while ($vevent{'data'} =~ m/ATTENDEE;(.+)$/gm) {
my @parameters = split(';', $1);
$vevent{'attendees'} = [] unless ($vevent{'attendees'});
my %attendee = ();
foreach (@parameters) {
#print $_,"\n";
if (m/^(\S+)=(.+)$/) {
print "\t$1 => $2\n";
$attendee{$1} = $2;
if ($1 eq 'CN' && $2 =~ m/mailto:(\S+)$/) {
$attendee{'CN'} = $1;
$attendee{'username'} = &getUsernameByEmail($ldap, $1);
$hasAttendees = 1 if ($1 ne $email); # Attendee is not the owner
}
}
}
push(@{$vevent{'attendees'}}, \%attendee);
}
# handle duplicated UID within file
if ($duplicatedUID{$uid}) {
$uid .= $duplicatedUID{$uid};
$duplicatedUID{$vevent{'uid'}}++;
}
else {
$duplicatedUID{$uid} = 1;
}
unless (DUPLICATES eq 'update') {
if (&searchByUid($username, $uid)) {
print STDERR "Event with UID '$uid' already exists\n";
return 0 if (DUPLICATES eq 'ignore');
if (DUPLICATES eq 'replace') {
&deleteEvent($username, $uid);
}
# elsif ($hasAttendees) {
# print STDERR "UID collision (",$uid,") for an event with attendee(s); ignoring it\n";
# return 0;
# }
else {
# Make sure UID is unique (DUPLICATES eq 'create')
my $i = ($duplicatedUID{$vevent{'uid'}})?$duplicatedUID{$vevent{'uid'}}:1;
for ($uid .= $i;
&searchByUid($username, $uid) == 1;
print STDERR "Event with UID '$uid' already exists\n",
$uid = $vevent{'uid'} . $i,
$i++)
{};
$duplicatedUID{$vevent{'uid'}} = $i + 1;
}
}
}
# If UID already exists, change it in the VEVENT
if ($uid ne $vevent{'uid'}) {
$vevent{'data'} =~ s/^UID:\S+$/UID:$uid/m;
}
if ($vevent{'data'} =~ m/^SUMMARY:[;\s]*$/m) {
$vevent{'data'} =~ s#^(BEGIN:VEVENT)#$1\nSUMMARY: (untitled event)#m;
}
if ($vevent{'recurrent'}) {
$vevent{'data'} =~ s#^(BEGIN:VEVENT)#$1\nRRULE:FREQ=DAILY;COUNT=1;INTERVAL=1#m;
}
$vevent{'data'} =
"BEGIN:VCALENDAR\n" .
"VERSION:2.0\n" .
"PRODID:Oracle/Oracle Calendar Server 10.1.2.3.3\n" .
$vevent{'data'} .
"END:VCALENDAR";
if (DEBUG) {
foreach my $key (keys %vevent) {
if (ref($vevent{$key}) eq 'ARRAY') {
print "$key =>\n";
foreach (@{$vevent{$key}}) {
my %hash = %{$_};
print " =>";
foreach (keys %hash) {
print "\t$_ => $hash{$_}\n";
}
}
}
else {
print "$key = \n\t", $vevent{$key},"\n";# unless ($key eq 'data');
}
}
}
print "PUT ",&calendarUrl($username, $uid),"\n";
return 0 if (DRYRUN);
my $request = HTTP::Request->new();
$request->method('PUT');
$request->uri(&calendarUrl($username, $uid));
$request->header('Authorization' => "Basic $pwdhash");
#$request->header('Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7');
#$request->header('Accept-Language' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3');
#$request->header('Content-Type' => 'text/plain; charset=utf-8');
$request->header('Content-Type' => 'text/calendar; charset=utf-8');
$request->header('Content-Length' => length($vevent{'data'}));
$request->header('x-sogo-mode' => 'M');
#$request->header('Connection' => 'TE');
if (FORCE_CLOSE && ($count % FORCE_CLOSE) == 0) {
print "Force connection close (no keepalive)\n";
$request->header('Connection' => 'close');
}
#$request->header('TE' => 'trailers');
#$request->header('Depth' => 1);
#$request->header('Accept-Charset' => 'utf-8');
#$request->header('Accept' => 'text/plain');
$request->content($vevent{'data'});
return &httpRequest($request, $uid);
# my $i;
# for ($i = 0; $i < 30; $i++) {
# my $response = $ua->request($request);
# if ($response->is_success) {
# print "PUT $uid:\t", $response->status_line, "\n";
# last;
# }
# else {
# print STDERR "ERR PUT $uid:\t", $response->status_line, "\n";
# sleep(2);
# }
# }
#
# if ($i == 30) {
# print STDERR "ERR PUT $uid:\tCan't reach server for the past 60 secs - exiting.\n";
# exit(-4);
# }
}
sub parseEventsFile
{
my $file = shift;
my %vevent = ();
my %last_vevent = ();
# data
# uid
# encoding
# organizer
# username
# recurrent
my $count = 0;
my $count_created = 0;
my $bytes_count = 0;
my $elapsed_time = [gettimeofday];
while (my $line = <CAL>) {
$line =~ s/\r$//; # remove dos linebreaks
if ($line =~ m/^BEGIN:VEVENT$/) {
$vevent{'data'} = $line;
}
elsif ($line =~ m/^END:VEVENT$/) {
$vevent{'data'} .= $line;
#if ($vevent{'organizer'} eq $email) {
$count++;
$bytes_count += length($vevent{'data'});
if (RECURRENT eq 'smart') {
if (%last_vevent) {
if ($last_vevent{'uid'} eq $vevent{'uid'}) {
$last_vevent{'data'} .= $vevent{'data'};
$last_vevent{'recurrent'} = 1;
if ($last_vevent{'username'} ne $vevent{'username'}) {
print "ERR: Matching UID with different organizers!\n";
}
}
else {
$count_created += &putEvent(\%last_vevent, $count);
%last_vevent = %vevent;
}
}
else {
%last_vevent = %vevent;
}
}
# elsif ($vevent{'rdate'}) {
# # Ignore event with RDATE attributes -- they are not currently
# # supported in SOGo (web)
# $vevent{'rdate'} = undef;
# print "Event with RDATE -- ignored\n";
# }
else {
$count_created += &putEvent(\%vevent, $count);
}
#$last_data = $vevent{'data'};
#last;
#}
#else {
#print $vevent{'uid'},": $email ($username) NOT organizer ",$vevent{'organizer'}," (",$vevent{'username'},"); verify event\n";
#}
$vevent{'data'} = undef;
#last;
}
elsif ($vevent{'data'}) {
if ($line !~ m/^$/
&& $line !~ m/^RECURRENCE-ID/
&& $line !~ m/^RDATE:/) {
if ($line =~ m/UID:\s*(\S+)$/) {
$vevent{'uid'} = $1;
$vevent{'uid'} =~ s/[#&\/]/-/g;
$vevent{'uid'} =~ s/\.//g;
$line =~ s/^(UID:).*$/$1$vevent{'uid'}/;
}
elsif ($line =~ m/^ORGANIZER:(?:mailto:)?(\S+)$/) {
$vevent{'organizer'} = $1;
$vevent{'username'} = &getUsernameByEmail($ldap, $1);
}
# elsif ($line =~ m/^RDATE:/) {
# $vevent{'rdate'} = 1;
# }
$vevent{'data'} .= $line unless ();
}
else {
print "ignored: '$line'\n";
}
}
elsif ($line =~ m/Content-Transfer-Encoding: (\S+)$/) {
$vevent{'encoding'} = $1;
}
}
if (%last_vevent) {
$count_created += &putEvent(\%last_vevent, $count);
}
printf "\nParsed %i events, %i new: %.1f KB in %.1f seconds\n",
$count, $count_created, ($bytes_count/1024), tv_interval($elapsed_time);
return 1;
}
sub gmtTime {
my $time = localtime(shift);
#my ($second,$minute,$hour,$dayofmonth,$month,$year,$weekday,$dayofyear,$isdst) = localtime($time);
#$year += 1900;
#$month++;
#$hour -= $isdst;
return sprintf("%04d%02d%02dT%02d%02d%02dZ",
$time->year+1900,
$time->mon+1,
$time->mday,
$time->hour-$time->isdst,
$time->min,
$time->isdst);
}
sub putTask(\%) {
my (%task) = %{(shift)};
my $count = shift;
my $bytes_count_ref = shift;
return 0 unless ($task{'summary'});
my $now = &gmtTime(time);
my $uid = md5_hex(%task);
my $data = <<'VCAL';
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Inverse inc.//SOGo 0.9//EN
BEGIN:VTODO
UID:%s
SUMMARY:%s
LOCATION:
VCAL
$data = sprintf($data, uc($uid), $task{'summary'});
$data .= "PRIORITY:" . $task{'priority'} . "\n" if ($task{'priority'});
$data .= "CREATED:$now\n";
$data .= "DTSTAMP:$now\n";
$data .= "LAST-MODIFIED:$now\n";
$data .= "DTSTART:" . &gmtTime($task{'start'}) . "\n" if ($task{'start'});
$data .= "DUE:" . &gmtTime($task{'end'}) . "\n" if ($task{'end'});
if (defined($task{'completion'})) {
if (scalar($task{'completion'}) < 100) {
$data .= "STATUS:IN-PROCESS\n";
}
else {
$data .= "STATUS:COMPLETED\n";
}
$data .= "PERCENT-COMPLETE:" . $task{'completion'} . "\n";
}
$data .= "DESCRIPTION:" . join("\\r\\n", @{$task{'description'}}) . "\n" if ($task{'description'});
$data .= "END:VTODO\n";
$data .= "END:VCALENDAR\n";
$$bytes_count_ref += length($data);
print $data if (DEBUG);
print "PUT ",&calendarUrl($username, $uid),"\n";
return 0 if (DRYRUN);
my $request = HTTP::Request->new();
$request->method('PUT');
$request->uri(&calendarUrl($username, $uid));
$request->header('Authorization' => "Basic $pwdhash");
$request->header('Content-Type' => 'text/calendar; charset=utf-8');
$request->header('Content-Length' => length($data));
$request->header('x-sogo-mode' => 'M');
if (FORCE_CLOSE && ($count % FORCE_CLOSE) == 0) {
print "Force connection close (no keepalive)\n";
$request->header('Connection' => 'close');
}
$request->content($data);
return &httpRequest($request, $uid);
}
sub parseTasksFile {
# S 9265740
# D 9266220
# T task august 13th
# R 1
# L 100
# M bar foo
# W bar foo
# C task august 13th 2008
# C line 2 description
# C line 3
# O
# BEGIN:VCALENDAR
# VERSION:2.0
# PRODID:-//Inverse inc.//SOGo 0.9//EN
# BEGIN:VTODO
# UID:26A-4979F880-1-B72F03D0
# SUMMARY:this is a task
# LOCATION:there
# PRIORITY:1
# STATUS:IN-PROCESS
# CREATED:20090123T170443Z
# DTSTAMP:20090123T170443Z
# LAST-MODIFIED:20090123T170443Z
# DTSTART:20090123T171500Z
# DUE:20090124T181500Z
# PERCENT-COMPLETE:40
# DESCRIPTION:foo
# END:VTODO
# END:VCALENDAR
#my $file = $_[0];
my $count = 0;
my $count_created = 0;
my $bytes_count = 0;
my $elapsed_time = [gettimeofday];
my %task = ();
# Start and due times are computed in minutes since since Jan 1 1991
my $basetime = timelocal(0, 0, 0, 1, 0, 91);
# my $tm = localtime($basetime);
# printf("Base date: %04d/%02d/%02d %02d:%02d:%02d\n",
# $tm->year+1900, $tm->mon+1, $tm->mday,
# $tm->hour, $tm->min, $tm->sec);
# open (my $tasksfile, $file)
# or die "Cannot open tasks file '$file'";
# while ($line = <$tasksfile>) {
while (my $line = <CAL>) {
#$line =~ s/\n$//;
chomp $line;
if ($line =~ m/^O/) {
if (%task) {
$count++;
$count_created += &putTask(\%task, $count, \$bytes_count);
%task = ();
}
}
elsif ($line =~ m/^T (.+)/) {
$task{'summary'} = $1;
}
elsif ($line =~ m/^S (\d+)/ && $1) {
$task{'start'} = $1*60 + $basetime;
}
elsif ($line =~ m/^D (\d+)/ && $1) {
# End time (number of minutes since Jan 1 1991)
$task{'end'} = $1*60 + $basetime;
# $tm = localtime($task{'end'});
# printf("End date: %04d/%02d/%02d %02d:%02d:%02d\n",
# $tm->year+1900, $tm->mon+1, $tm->mday,
# $tm->hour, $tm->min, $tm->sec);
}
elsif ($line =~ m/^R (\w+)/) {
$task{'priority'} = $1;
}
elsif ($line =~ m/^L (\w+)/) {
$task{'completion'} = $1;
}
elsif ($line =~ m/^C (.+)/) {
$task{'description'} = () unless ($task{'description'});
push(@{$task{'description'}}, $1);
}
}
#close ($tasksfile);
close (CAL);
printf "\nParsed %i tasks, %i new: %.1f KB in %.1f seconds\n",
$count, $count_created, ($bytes_count/1024), tv_interval($elapsed_time);
return 1;
}
sub parseRightsFile
{
my $file = $_[0];
open (my $rightsfile, $file)
or die "Cannot open rights file '$file'";
my $line = <$rightsfile>;
$line =~ s/\n$//;
#line:---procuration, username, foo.bar@foo.edu
my $user;
if ($line =~ m@^\-\-\-procuration, ([^,]+),@) {
$user = $1;
print "rights for user's calendar: $user\n";
}
else {
die "Could not parse procuration line: $line";
}
my $next = 0; # 0 = Grantee, 1 = Designate right
my $grantee;
my $rights;
while ($line = <$rightsfile>) {
$line =~ s/\n$//;
if ($next == 0) {
if ($line =~ m@^Grantee:\ S=[^/]+/G=[^/]+/UID=([^/]+)/ID=[^/]+/NODE\-ID=[^/]+$@) {
$grantee = $1;
}
else {
die "Expected or mal-formed 'Grantee' line: $line";
}
$next = 1;
}
elsif ($next == 1) {
if ($line =~ m@^Designate\ Right:\ (.*)$@) {
my $oracleRights = $1;
$rights = &convertOracleRights($oracleRights);
}
else {
die "Expected or mal-formed 'Designate Right' line: $line";
}
&grantUserRights($grantee, $rights, $user);
$next = 0;
}
}
close ($rightsfile);
}
#line:Designate Right: CONFIDENTIALEVENT=NONE/CONFIDENTIALTASK=NONE/NORMALEVENT=MODIFY/NORMALTASK=MODIFY/PERSONALEVENT=VIEWTIME/PERSONALTASK=NONE/PUBLICEVENT=MODIFY/PUBLICTASK=MODIF
sub convertOracleRights()
{
my $oracleRights = $_[0];
my %keyMapping = ( 'CONFIDENTIAL' => 'Confidential',
'NORMAL' => 'Public',
'PUBLIC' => 'Public',
'PERSONAL' => 'Private' );
my %valueMapping = ( 'VIEW' => 'Viewer', # à confirmer
'VIEWTIME' => 'DAndTViewer',
'MODIFY' => 'Modifier',
'REPLY' => 'Responder' );
my %rights = ();
my @parsedRights = split('/', $oracleRights);
foreach my $parsedRight (@parsedRights) {
my ($key, $value) = split('=', $parsedRight);
if ($key =~ /(.*)EVENT$/ && $value ne 'NONE') {
$key = $1;
die "No mapping found for key '$key'"
unless defined $keyMapping{$key};
die "No mapping found for value '$value'"
unless defined $valueMapping{$value};
$rights{$keyMapping{$key}.$valueMapping{$value}} = 1;
}
}
return [keys %rights];
}
sub grantUserRights()
{
my ($grantee, $rights, $user) = @_;
die "No grantee specified"
unless defined $grantee;
die "No rights specified"
unless defined $rights;
die "No user specified"
unless defined $user;
my $xmlRights = "";
foreach my $right (@$rights) {
$xmlRights .= "<$right/>";
}
my $content = ( '<?xml version="1.0" encoding="UTF-8"?>' . "\n"
. '<acl-query xmlns="urn:inverse:params:xml:ns:inverse-dav"><set-roles user='
. '"' . $user . '">' . $xmlRights . '</set-roles></acl-query>' );
my $request = HTTP::Request->new();
$request->method('POST');
$request->uri(&calendarUrl($user));
$request->header('Authorization' => "Basic $pwdhash");
$request->header('Content-Type' => 'application/xml');
$request->header('Content-Length' => length($content));
$request->content($content);
my $result = &httpRequest($request, $username);
my $response = $ua->request($request);
}
##
## MAIN
##
if ($#ARGV < 3) {
&usage();
exit(-1);
}
$url = $ARGV[0];
$username = $ARGV[1];
my $type = $ARGV[2];
$file = $ARGV[3];
if ($type ne 'events' && $type ne 'tasks' && $type ne 'rights') {
usage("The argument 'type' does not have a proper value: '$type'");
exit(-1);
}
# Prepare LDAP connection
$ldap = new Net::LDAP(LDAP_HOST) or die "Can't connect to LDAP server: $@.\n";
my $msg = $ldap->bind(LDAP_BIND_DN, password => LDAP_BIND_PW);
if ($msg->is_error()) {
die "Can't bind to LDAP server: ".$msg->error()."\n";
}
# Verify file name format; extract username
#if ($file =~ m/^(?!.+?\W)?events\.(\d{8}|invite\d+)$/) {
#if ($file =~ m/^(?:.+\/)?(events|tasks|rights)\.(\d{8}|invite\d+|[a-z]+)(\.test\d?)?$/) {
# $username = $2;
$email = &getEmailByUsername($ldap, $username);
print "$username = $email\n";
if (FORCE_USERNAME) {
$username = FORCE_USERNAME;
print "Force username to $username\n";
}
# Open iCalendar file
open (CAL, $file) or die "Can't open file $file: $!\n";
# Prepare HTTP query
if ($url =~ m#^(https?://)(?:([^:]+):([^@]+)@)?([^/]+)#) {
($authusername, $password, $host) = ($2, $3, $4);
$url = $1.$4;
if ($host =~ m/:(\d+)/) {
$port = $1;
}
elsif ($url =~ m/^https/) {
$port = '443';
$host .= ":$port";
}
else {
$port = '80';
$host .= ":$port";
}
# print "host = $host, auth = $authusername\n";
}
else {
&usage("The URL doesn't have the proper format.");
exit(-3);
}
$pwdhash = encode_base64($authusername . ':' . $password);
$ua = LWP::UserAgent->new();
$ua->agent('Mozilla/5.0');
$ua->timeout(1800);
# Verify is user personal calendar exists (or can be automatically created)
die "Can't access personal calendar of username $username\n" unless (&userCalendarExists($username));
my $parsers = { 'events' => \&parseEventsFile,
'tasks' => \&parseTasksFile,
'rights' => \&parseRightsFile };
$parsers->{$type}($file);
exit;

4
NEWS
View File

@ -1,10 +1,12 @@
1.3-201008xx (1.3.1)
1.3-20100819 (1.3.1)
--------------------
- added migration scripts for Horde (email signatures and address books)
- added migration script for Oracle Calendar (events, tasks and access rights)
- added Polish translation
- added crypt support to SQL sources
- updated Ukrainian translation
- added the caldav-auto-schedule capability
- improved support for IE8
1.3-20100721 (1.3.0)
--------------------

View File

@ -6,7 +6,9 @@ setup
1) copy config.py.in to config.py (make sure to never EVER add it to monotone)
2) edit config.py to suit your environment
3) run the test scripts
3) make sure that you use a fresh database, with no prior information in it
4) make sure that SOGoCalendarDefaultRoles and SOGoContactsDefaultRoles are empty or undefined
5) run the test scripts
runnable scripts
----------------

View File

@ -1,3 +1,13 @@
# Version file
<<<<<<< variant A
SUBMINOR_VERSION:=1
# v0.9.1 requires Main v0.9.59
>>>>>>> variant B
SUBMINOR_VERSION:=1
####### Ancestor
SUBMINOR_VERSION:=0
# v0.9.1 requires Main v0.9.59
======= end

View File

@ -24,11 +24,12 @@ var SOGoResizableTableInterface = {
}
SOGoResizableTable._resize(this, $(cell), i, null, cell.getWidth());
}
this.computeColumnsWidths();
Event.observe(window, "resize", this.resize.bind(this));
},
resize: function(e) {
// Only resize the columns after a certain delay, otherwise it slow
// Only resize the columns after a certain delay, otherwise it slows
// down the interface.
if (this.delayedResize) window.clearTimeout(this.delayedResize);
this.delayedResize = this._resize.bind(this).delay(0.2);
@ -62,7 +63,7 @@ var SOGoResizableTableInterface = {
this.ratios = relativeWidths;
},
saveColumnsState: function() {
computeColumnsWidths: function() {
this.ratios = new Hash();
var tableWidth = 100/this.getWidth();
var cells = $(this).down('tr').childElements();
@ -71,6 +72,10 @@ var SOGoResizableTableInterface = {
if (cell.hasClassName('resizable'))
this.ratios.set(cell.id, Math.round(cell.getWidth()*tableWidth));
}
},
saveColumnsState: function() {
this.computeColumnsWidths();
if (!$(document.body).hasClassName("popup")) {
var url = ApplicationBaseURL + "saveColumnsState";
var data = this.ratios;

View File

@ -251,7 +251,6 @@ function _editFilter(filterId) {
}
function onFilterAdd(event) {
log("onFilterAdd");
_editFilter("new");
event.stop();
}