merge of '3f8608c82d9c379cc9e0fea6ffe853cc1949a24b'

and '7634b9d503d61b6240548e75e7f872081debc6b4'

Monotone-Parent: 3f8608c82d9c379cc9e0fea6ffe853cc1949a24b
Monotone-Parent: 7634b9d503d61b6240548e75e7f872081debc6b4
Monotone-Revision: a4c7e35d642045e63467a131fae8049524f450e4

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2012-06-30T18:20:30
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Francis Lachapelle 2012-06-30 18:20:30 +00:00
commit 09504ed1cb
26 changed files with 500 additions and 177 deletions

View File

@ -3,6 +3,38 @@
* SoObjects/SOGo/SOGoObject.m (-initWithName:inContainer:): make * SoObjects/SOGo/SOGoObject.m (-initWithName:inContainer:): make
sure that "_name" is neither nil nor empty. sure that "_name" is neither nil nor empty.
2012-06-27 Jean Raby <jraby@inverse.ca>
* SoObjects/Appointments/SOGoAppointmentObject.m
(PUTAction:): detect conflicting event UID and
deny the request accordingly.
2012-06-21 Ludovic Marcotte <lmarcotte@inverse.ca>
* Added the SOGoSearchMinimumWordLength domain
default which controls the minimal length required
before trigging server-side search operations for
attendee completion, contact searches, etc. The
default value is 2, which means search operations
are trigged once the 3rd character is typed.
2012-06-20 Ludovic Marcotte <lmarcotte@inverse.ca>
* SoObjects/Appointments/SOGoAppointmentObject.m
(-_handleResourcesConflicts:forEvent:): We now
handle correctly recurring events overlapping other
recurring events.
2012-06-19 Ludovic Marcotte <lmarcotte@inverse.ca>
* SoObjects/Appointments/SOGoAppointmentFolder.m (-importCalendar:):
We now handle correctly floating events by forcing the use of
the user's timezone.
* SoObjects/Appointments/SOGoCalendarComponent.m (-expandGroupsInEvent:):
We now remove all attendees that are equal (email-based comparison) to
the event's organizer instead of only for decomposed groups.
2012-06-12 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2012-06-12 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoGroup.m * SoObjects/SOGo/SOGoGroup.m

12
NEWS
View File

@ -1,3 +1,14 @@
1.3.17 (2012-MM-DD)
-------------------
New Features
-
Enhancements
- updated Czech translation
Bug Fixes
-
1.3.16 (2012-06-07) 1.3.16 (2012-06-07)
------------------- -------------------
Enhancements Enhancements
@ -8,6 +19,7 @@ Enhancements
- it's no longer possible to click the "Upload" button multiple times - it's no longer possible to click the "Upload" button multiple times
- allow delivery of mail with no subject, but alert the user - allow delivery of mail with no subject, but alert the user
- updated Dutch, German, French translations - updated Dutch, German, French translations
Bug Fixes Bug Fixes
- fixed compilation under GNU/kFreeBSD - fixed compilation under GNU/kFreeBSD
- fixed compilation for arm architecture - fixed compilation for arm architecture

View File

@ -180,8 +180,8 @@
- (NSString *) createSessionsFolderWithName: (NSString *) tableName - (NSString *) createSessionsFolderWithName: (NSString *) tableName
{ {
static NSString *sqlFolderFormat static NSString *sqlFolderFormat
= (@"CREATE TABLE %@ (" = (@"CREATE TABLE %@ ("
@" c_id VARCHAR(255) PRIMARY KEY," @" c_id VARCHAR(255) NOT NULL PRIMARY KEY,"
@" c_value VARCHAR(255) NOT NULL," @" c_value VARCHAR(255) NOT NULL,"
@" c_creationdate INT4 NOT NULL," @" c_creationdate INT4 NOT NULL,"
@" c_lastseen INT4 NOT NULL)"); @" c_lastseen INT4 NOT NULL)");

View File

@ -67,4 +67,4 @@ vtodo_class2 = "(Skrytý úkol)";
= "%{Attendee} %{SentByText}dosud o Vaší pozvánce k události nerozhodl/a."; = "%{Attendee} %{SentByText}dosud o Vaší pozvánce k události nerozhodl/a.";
/* Resources */ /* Resources */
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Maximální počet současných rezervací (%{NumberOfSimultaneousBookings}) byl dosažen pro zdroj \"%{Cn} %{SystemEmail}\"."; "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Byl dosažen maximální počet současných rezervací\n(%{NumberOfSimultaneousBookings}) pro zdroj \"%{Cn} %{SystemEmail}\". Konfliktní událost je \"%{EventTitle}\" a začíná %{StartDate}.";

View File

@ -2764,7 +2764,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
iCalEvent *event; iCalEvent *event;
int imported, count, i; int imported, count, i;
imported = 0; imported = 0;
if (calendar) if (calendar)
@ -2799,6 +2799,41 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
tzId = [startDate value: 0 ofAttribute: @"tzid"]; tzId = [startDate value: 0 ofAttribute: @"tzid"];
if ([tzId length]) if ([tzId length])
timezone = [timezones valueForKey: tzId]; timezone = [timezones valueForKey: tzId];
else
{
// If the start date is a "floating time", let's use the user's timezone
// during the import for both the start and end dates.
NSString *s;
s = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
if ([element isKindOfClass: [iCalEvent class]] &&
![(iCalEvent *)element isAllDay] &&
![s hasSuffix: @"Z"] &&
![s hasSuffix: @"z"])
{
iCalDateTime *endDate;
int delta;
timezone = [iCalTimeZone timeZoneForName: [[[self->context activeUser] userDefaults] timeZoneName]];
[calendar addTimeZone: timezone];
delta = [[timezone periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
event = (iCalEvent *)element;
[event setStartDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
[startDate setTimeZone: timezone];
endDate = (iCalDateTime *) [element uniqueChildWithTag: @"dtend"];
if (endDate)
{
[event setEndDate: [[event endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
[endDate setTimeZone: timezone];
}
}
}
if ([element isKindOfClass: [iCalEvent class]]) if ([element isKindOfClass: [iCalEvent class]])
{ {
event = (iCalEvent *)element; event = (iCalEvent *)element;

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2007-2012 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOGo This file is part of SOGo
@ -456,8 +456,12 @@
{ {
SOGoAppointmentFolder *folder; SOGoAppointmentFolder *folder;
NSCalendarDate *start, *end; NSCalendarDate *start, *end;
NGCalendarDateRange *range;
NSMutableArray *fbInfo; NSMutableArray *fbInfo;
int i; NSArray *allOccurences;
BOOL must_delete;
int i, j;
// We get the start/end date for our conflict range. If the event to be added is recurring, we // We get the start/end date for our conflict range. If the event to be added is recurring, we
// check for at least a year to start with. // check for at least a year to start with.
@ -486,12 +490,50 @@
// We first remove any occurences in the freebusy that corresponds to the // We first remove any occurences in the freebusy that corresponds to the
// current event. We do this to avoid raising a conflict if we move a 1 hour // current event. We do this to avoid raising a conflict if we move a 1 hour
// meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise. // meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise.
//
// We must also check here for repetitive events that don't overlap our event.
// We remove all events that don't overlap. The events here are already
// decomposed.
//
if ([theEvent isRecurrent])
allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end]
firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate]
endDate: [theEvent endDate]]];
else
allOccurences = nil;
for (i = [fbInfo count]-1; i >= 0; i--) for (i = [fbInfo count]-1; i >= 0; i--)
{ {
range = [NGCalendarDateRange calendarDateRangeWithStartDate: [[fbInfo objectAtIndex: i] objectForKey: @"startDate"]
endDate: [[fbInfo objectAtIndex: i] objectForKey: @"endDate"]];
if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame) if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame)
[fbInfo removeObjectAtIndex: i]; {
} [fbInfo removeObjectAtIndex: i];
continue;
}
// No need to check if the event isn't recurrent here as it's handled correctly
// when we compute the "end" date.
if ([allOccurences count])
{
must_delete = YES;
for (j = 0; j < [allOccurences count]; j++)
{
if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]])
{
must_delete = NO;
break;
}
}
if (must_delete)
[fbInfo removeObjectAtIndex: i];
}
}
if ([fbInfo count]) if ([fbInfo count])
{ {
// If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit
@ -1714,15 +1756,25 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
iCalCalendar *calendar; iCalCalendar *calendar;
SOGoUser *ownerUser; SOGoUser *ownerUser;
iCalEvent *event; iCalEvent *event, *conflictingEvent;
NSString *eventUID;
BOOL scheduling; BOOL scheduling;
calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]];
event = [[calendar events] objectAtIndex: 0]; event = [[calendar events] objectAtIndex: 0];
eventUID = [event uid];
ownerUser = [SOGoUser userWithLogin: owner]; ownerUser = [SOGoUser userWithLogin: owner];
scheduling = [self _shouldScheduleEvent: [event organizer]]; scheduling = [self _shouldScheduleEvent: [event organizer]];
// make sure eventUID doesn't conflict with an existing event - see bug #1853
// TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1)
if (conflictingEvent = [container resourceNameForEventUID: eventUID])
{
NSString *reason = [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID];
return [NSException exceptionWithHTTPStatus:403 reason: reason];
}
// //
// New event and we're the organizer -- send invitation to all attendees // New event and we're the organizer -- send invitation to all attendees

View File

@ -516,19 +516,23 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
// //
// Returs "YES" if a a group was decomposed among attendees. // Returs "YES" if a a group was decomposed among attendees.
// //
// It can also return yes if an attendee was found in the list
// matching the organizer. In which case, it was removed.
//
- (BOOL) expandGroupsInEvent: (iCalEvent *) theEvent - (BOOL) expandGroupsInEvent: (iCalEvent *) theEvent
{ {
NSMutableArray *allAttendees;
NSEnumerator *enumerator;
NSString *organizerEmail, *domain; NSString *organizerEmail, *domain;
NSMutableArray *allAttendees;
iCalPerson *currentAttendee; iCalPerson *currentAttendee;
NSEnumerator *enumerator;
SOGoGroup *group; SOGoGroup *group;
BOOL doesIncludeGroup;
BOOL eventWasModified;
unsigned int i; unsigned int i;
domain = [[context activeUser] domain]; domain = [[context activeUser] domain];
organizerEmail = [[theEvent organizer] rfc822Email]; organizerEmail = [[theEvent organizer] rfc822Email];
doesIncludeGroup = NO; eventWasModified = NO;
allAttendees = [NSMutableArray arrayWithArray: [theEvent attendees]]; allAttendees = [NSMutableArray arrayWithArray: [theEvent attendees]];
enumerator = [[theEvent attendees] objectEnumerator]; enumerator = [[theEvent attendees] objectEnumerator];
while ((currentAttendee = [enumerator nextObject])) while ((currentAttendee = [enumerator nextObject]))
@ -548,7 +552,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
for (i = 0; i < [members count]; i++) for (i = 0; i < [members count]; i++)
{ {
user = [members objectAtIndex: i]; user = [members objectAtIndex: i];
doesIncludeGroup = YES; eventWasModified = YES;
// If the organizer is part of the group, we skip it from // If the organizer is part of the group, we skip it from
// the addition to the attendees' list // the addition to the attendees' list
@ -565,12 +569,23 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
[allAttendees addObject: person]; [allAttendees addObject: person];
} }
} }
} else
{
// We remove any attendees matching the organizer. Apple iCal will do that when
// you invite someone. It'll add the organizer in the attendee list, which will
// confuse itself!
if ([[currentAttendee rfc822Email] caseInsensitiveCompare: organizerEmail] == NSOrderedSame)
{
[allAttendees removeObject: currentAttendee];
eventWasModified = YES;
}
}
} // while (currentAttendee ...
if (doesIncludeGroup) if (eventWasModified)
[theEvent setAttendees: allAttendees]; [theEvent setAttendees: allAttendees];
return doesIncludeGroup; return eventWasModified;
} }
- (void) _updateRecurrenceIDsWithEvent: (iCalRepeatableEntityObject*) newEvent - (void) _updateRecurrenceIDsWithEvent: (iCalRepeatableEntityObject*) newEvent

View File

@ -70,4 +70,6 @@
SOGoReminderEnabled = YES; SOGoReminderEnabled = YES;
SOGoRemindWithASound = YES; SOGoRemindWithASound = YES;
SOGoSearchMinimumWordLength = 2;
} }

View File

@ -77,6 +77,8 @@
- (BOOL) hideSystemEMail; - (BOOL) hideSystemEMail;
- (int) searchMinimumWordLength;
@end @end
#endif /* SOGODOMAINDEFAULTS_H */ #endif /* SOGODOMAINDEFAULTS_H */

View File

@ -319,4 +319,9 @@
return [self boolForKey: @"SOGoHideSystemEMail"]; return [self boolForKey: @"SOGoHideSystemEMail"];
} }
- (int) searchMinimumWordLength
{
return [self integerForKey: @"SOGoSearchMinimumWordLength"];
}
@end @end

View File

@ -4,6 +4,9 @@
# attendee1_delegate_username and superuser. # attendee1_delegate_username and superuser.
# when writing new tests, avoid using superuser when not absolutely needed # when writing new tests, avoid using superuser when not absolutely needed
# TODO
# - Individual tests should set the ACLs themselves on Resources tests
from config import hostname, port, username, password, \ from config import hostname, port, username, password, \
superuser, superuser_password, \ superuser, superuser_password, \
attendee1, attendee1_username, \ attendee1, attendee1_username, \
@ -124,6 +127,8 @@ class CalDAVSchedulingTest(unittest.TestCase):
self.user_calendar = "/SOGo/dav/%s/Calendar/personal/" % username self.user_calendar = "/SOGo/dav/%s/Calendar/personal/" % username
self.attendee1_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1 self.attendee1_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1
self.attendee1_delegate_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1_delegate self.attendee1_delegate_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1_delegate
self.res_calendar = "/SOGo/dav/%s/Calendar/personal/" % resource_no_overbook
self.res_ob_calendar = "/SOGo/dav/%s/Calendar/personal/" % resource_can_overbook
# fetch non existing event to let sogo create the calendars in the db # fetch non existing event to let sogo create the calendars in the db
self._getEvent(self.client, "%snonexistent" % self.user_calendar, exp_status=404) self._getEvent(self.client, "%snonexistent" % self.user_calendar, exp_status=404)
@ -131,35 +136,24 @@ class CalDAVSchedulingTest(unittest.TestCase):
self._getEvent(self.attendee1_delegate_client, "%snonexistent" % self._getEvent(self.attendee1_delegate_client, "%snonexistent" %
self.attendee1_delegate_calendar, exp_status=404) self.attendee1_delegate_calendar, exp_status=404)
# list of ics used by the test.
# tearDown will loop over this and wipe them in all users' calendar
self.ics_list = []
def tearDown(self): def tearDown(self):
self._deleteEvent(self.client, # delete all created events from all users' calendar
"%stest-delegation.ics" % self.user_calendar, None) for ics in self.ics_list:
self._deleteEvent(self.attendee1_client, self._deleteEvent(self.superuser_client,
"%stest-delegation.ics" % self.attendee1_calendar, None) "%s%s" % (self.user_calendar, ics), None)
self._deleteEvent(self.attendee1_delegate_client, self._deleteEvent(self.superuser_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar, "%s%s" % (self.attendee1_calendar, ics), None)
None) self._deleteEvent(self.superuser_client,
self._deleteEvent(self.client, "%s%s" % (self.attendee1_delegate_calendar, ics), None)
"%stest-add-attendee.ics" % self.user_calendar, None) self._deleteEvent(self.superuser_client,
self._deleteEvent(self.attendee1_client, "%s%s" % (self.res_calendar, ics), None)
"%stest-add-attendee.ics" % self.attendee1_calendar, None) self._deleteEvent(self.superuser_client,
self._deleteEvent(self.client, "%s%s" % (self.res_ob_calendar, ics), None)
"%stest-no-overbook.ics" % self.user_calendar, None)
self._deleteEvent(self.client,
"%stest-no-overbook-overlap.ics" % self.user_calendar, None)
self._deleteEvent(self.client,
"%stest-can-overbook.ics" % self.user_calendar, None)
self._deleteEvent(self.client,
"%stest-can-overbook-overlap.ics" % self.user_calendar, None)
self._deleteEvent(self.client,
"%stest-rrule-exception-invitation-dance.ics" % self.user_calendar, None)
self._deleteEvent(self.attendee1_client,
"%stest-rrule-exception-invitation-dance.ics" % self.attendee1_calendar, None)
self._deleteEvent(self.client,
"%stest-rrule-invitation-deleted-exdate-dance.ics" % self.user_calendar, None)
self._deleteEvent(self.attendee1_client,
"%stest-rrule-invitation-deleted-exdate-dance.ics" % self.attendee1_calendar, None)
def _newEvent(self, summary="test event", uid="test", transp=0): def _newEvent(self, summary="test event", uid="test", transp=0):
transparency = ("OPAQUE", "TRANSPARENT") transparency = ("OPAQUE", "TRANSPARENT")
@ -219,6 +213,25 @@ class CalDAVSchedulingTest(unittest.TestCase):
if exp_status is not None: if exp_status is not None:
self.assertEquals(delete.response["status"], exp_status) self.assertEquals(delete.response["status"], exp_status)
def _getAllEvents(self, client, collection, exp_status = 207):
propfind = webdavlib.WebDAVPROPFIND(collection, None)
client.execute(propfind)
if exp_status is not None:
self.assertEquals(propfind.response["status"], exp_status)
content = []
nodes = propfind.response["document"].findall('{DAV:}response')
for node in nodes:
responseHref = node.find('{DAV:}href').text
content += [responseHref]
return content
def _deleteAllEvents(self, client, collection, exp_status = 204):
content = self._getAllEvents(client, collection)
for item in content:
self._deleteEvent(client, item)
def _eventAttendees(self, event): def _eventAttendees(self, event):
attendees = {} attendees = {}
@ -268,7 +281,9 @@ class CalDAVSchedulingTest(unittest.TestCase):
""" add attendee after event creation """ """ add attendee after event creation """
# make sure the event doesn't exist # make sure the event doesn't exist
ics_name = "test-add-attendee.ics" ics_name = "test-add-attendee.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None) "%s%s" % (self.user_calendar,ics_name), None)
self._deleteEvent(self.attendee1_client, self._deleteEvent(self.attendee1_client,
@ -304,6 +319,8 @@ class CalDAVSchedulingTest(unittest.TestCase):
# make sure the event doesn't exist # make sure the event doesn't exist
ics_name = "test-remove-attendee.ics" ics_name = "test-remove-attendee.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None) "%s%s" % (self.user_calendar,ics_name), None)
self._deleteEvent(self.attendee1_client, self._deleteEvent(self.attendee1_client,
@ -346,104 +363,80 @@ class CalDAVSchedulingTest(unittest.TestCase):
# 6. verify that the attendee doesn't have the event anymore # 6. verify that the attendee doesn't have the event anymore
attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name), 404) attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name), 404)
def testAddAttendee(self): def testResourceNoOverbook(self):
""" add attendee after event creation """ """ try to overbook a resource """
# make sure there are no events in the resource calendar
self._deleteAllEvents(self.superuser_client, self.res_calendar)
# make sure the event doesn't exist # make sure the event doesn't exist
ics_name = "test-add-attendee.ics" ics_name = "test-no-overbook.ics"
self._deleteEvent(self.client, self.ics_list += [ics_name]
"%s%s" % (self.user_calendar,ics_name), None)
self._deleteEvent(self.client,
"%s%s" % (self.attendee1_calendar,ics_name), None)
# 1. create an event in the organiser's calendar
event = self._newEvent(summary="Test add attendee", uid="Test add attendee")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# 2. add an attendee
attendee = event.vevent.add('attendee')
attendee.cn_param = self.attendee1_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.attendee1_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
exp_status=204)
# 3. verify that the attendee has the event
attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
# 4. make sure the received event match the original one
# XXX is this enough?
self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
def testResourceNoOverbook(self):
""" try to overbook a resource """
# make sure the event doesn't exist
ics_name = "test-no-overbook.ics"
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None) "%s%s" % (self.user_calendar,ics_name), None)
ob_ics_name = "test-no-overbook-overlap.ics" ob_ics_name = "test-no-overbook-overlap.ics"
self._deleteEvent(self.client, self.ics_list += [ob_ics_name]
"%s%s" % (self.user_calendar,ics_name), None)
# 1. create an event in the organiser's calendar
event = self._newEvent(summary="Test no overbook", uid="test no overbook")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_no_ob_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_no_ob_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# 2. create a second event overlapping the first one
event = self._newEvent(summary="Test no overbook - overlap", uid="test no overbook - overlap")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_no_ob_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_no_ob_email
# put the event - should trigger a 403
self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event, exp_status=403)
def testResourceCanOverbook(self):
""" try to overbook a resource - multiplebookings=0"""
# make sure the event doesn't exist
ics_name = "test-can-overbook.ics"
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None)
ob_ics_name = "test-can-overbook-overlap.ics"
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ob_ics_name), None) "%s%s" % (self.user_calendar,ob_ics_name), None)
# 1. create an event in the organiser's calendar # 1. create an event in the organiser's calendar
event = self._newEvent(summary="Test can overbook", uid="test can overbook") event = self._newEvent(summary="Test no overbook", uid="test no overbook")
organizer = event.vevent.add('organizer') organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name organizer.cn_param = self.user_name
organizer.value = self.user_email organizer.value = self.user_email
attendee = event.vevent.add('attendee') attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_can_ob_name attendee.cn_param = self.res_no_ob_name
attendee.rsvp_param = "TRUE" attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION" attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_can_ob_email attendee.value = self.res_no_ob_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event) self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# 2. create a second event overlapping the first one # 2. create a second event overlapping the first one
event = self._newEvent(summary="Test can overbook - overlap", uid="test can overbook - overlap") event = self._newEvent(summary="Test no overbook - overlap", uid="test no overbook - overlap")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_no_ob_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_no_ob_email
# put the event - should trigger a 403
self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event, exp_status=403)
def testResourceCanOverbook(self):
""" try to overbook a resource - multiplebookings=0"""
# make sure there are no events in the resource calendar
self._deleteAllEvents(self.superuser_client, self.res_ob_calendar)
# make sure the event doesn't exist
ics_name = "test-can-overbook.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None)
ob_ics_name = "test-can-overbook-overlap.ics"
self.ics_list += [ob_ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ob_ics_name), None)
# 1. create an event in the organiser's calendar
event = self._newEvent(summary="Test can overbook", uid="test can overbook")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_can_ob_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_can_ob_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# 2. create a second event overlapping the first one
event = self._newEvent(summary="Test can overbook - overlap", uid="test can overbook - overlap")
organizer = event.vevent.add('organizer') organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name organizer.cn_param = self.user_name
organizer.value = self.user_email organizer.value = self.user_email
@ -453,8 +446,97 @@ class CalDAVSchedulingTest(unittest.TestCase):
attendee.partstat_param = "NEEDS-ACTION" attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_can_ob_email attendee.value = self.res_can_ob_email
# put the event - should be fine since we can overbook this one # put the event - should be fine since we can overbook this one
self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event) self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event)
def testResourceBookingOverlapDetection(self):
""" Resource booking overlap detection - bug #1837"""
# There used to be some problems with recurring events and resources booking
# This test implements these edge cases
# 1. Create recurring event (with resource)
# 2. Create single event overlaping one instance for the previous event
# (should fail)
# 3. Create recurring event which _doesn't_ overlap the first event
# (should be OK, used to fail pre1.3.17)
# 4. Create recurring event overlapping the previous recurring event
# (should fail)
# make sure there are no events in the resource calendar
self._deleteAllEvents(self.superuser_client, self.res_calendar)
# make sure the event doesn't exist
ics_name = "test-res-overlap-detection.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None)
overlap_ics_name = "test-res-overlap-detection-overlap.ics"
self.ics_list += [overlap_ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.attendee1_calendar,overlap_ics_name), None)
nooverlap_recurring_ics_name = "test-res-overlap-detection-nooverlap.ics"
self.ics_list += [nooverlap_recurring_ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,nooverlap_recurring_ics_name), None)
overlap_recurring_ics_name = "test-res-overlap-detection-overlap-recurring.ics"
self.ics_list += [overlap_recurring_ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,overlap_recurring_ics_name), None)
# 1. create recurring event with resource
event = self._newEvent(summary="recurring event with resource",
uid="recurring event w resource")
event.vevent.add('rrule').value = "FREQ=DAILY;COUNT=5"
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_no_ob_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_no_ob_email
# keep a copy around for #3
nooverlap_event = vobject.iCalendar()
nooverlap_event.copy(event)
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# 2. Create single event overlaping one instance for the previous event
event = self._newEvent(summary="recurring event with resource",
uid="recurring event w resource - overlap")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.attendee1_name
organizer.value = self.attendee1_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.res_no_ob_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.res_no_ob_email
# should fail
self._putEvent(self.client, "%s%s" % (self.attendee1_calendar, overlap_ics_name), event, exp_status=403)
# 3. Create recurring event which _doesn't_ overlap the first event
# (should be OK, used to fail pre1.3.17)
# shift the start date to one hour after the original event end time
nstartdate = nooverlap_event.vevent.dtend.value + datetime.timedelta(0, 3600)
nooverlap_event.vevent.dtstart.value = nstartdate
nooverlap_event.vevent.dtend.value = nstartdate + datetime.timedelta(0, 3600)
nooverlap_event.vevent.uid.value = "recurring - nooverlap"
self._putEvent(self.client, "%s%s" % (self.user_calendar, nooverlap_recurring_ics_name), nooverlap_event)
# 4. Create recurring event overlapping the previous recurring event
# should fail
nstartdate = nooverlap_event.vevent.dtstart.value + datetime.timedelta(0, 300)
nooverlap_event.vevent.dtstart.value = nstartdate
nooverlap_event.vevent.dtend.value = nstartdate + datetime.timedelta(0, 3600)
nooverlap_event.vevent.uid.value = "recurring - overlap"
self._putEvent(self.client, "%s%s" % (self.user_calendar, overlap_recurring_ics_name), nooverlap_event, exp_status=403)
def testRruleExceptionInvitationDance(self): def testRruleExceptionInvitationDance(self):
@ -474,6 +556,8 @@ class CalDAVSchedulingTest(unittest.TestCase):
# bob isn't in the master+exception event # bob isn't in the master+exception event
ics_name = "test-rrule-exception-invitation-dance.ics" ics_name = "test-rrule-exception-invitation-dance.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%s%s" % (self.user_calendar, ics_name), None) "%s%s" % (self.user_calendar, ics_name), None)
self._deleteEvent(self.attendee1_client, self._deleteEvent(self.attendee1_client,
@ -596,6 +680,8 @@ class CalDAVSchedulingTest(unittest.TestCase):
# and that bob is 'declined' # and that bob is 'declined'
ics_name = "test-rrule-invitation-deleted-exdate-dance.ics" ics_name = "test-rrule-invitation-deleted-exdate-dance.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%s%s" % (self.user_calendar, ics_name), None) "%s%s" % (self.user_calendar, ics_name), None)
self._deleteEvent(self.attendee1_client, self._deleteEvent(self.attendee1_client,
@ -663,17 +749,89 @@ class CalDAVSchedulingTest(unittest.TestCase):
self.assertEqual(org_ev_master.attendee.partstat_param, "NEEDS-ACTION"); self.assertEqual(org_ev_master.attendee.partstat_param, "NEEDS-ACTION");
self.assertEqual(org_ev_exception.attendee.partstat_param, "DECLINED"); self.assertEqual(org_ev_exception.attendee.partstat_param, "DECLINED");
def testOrganizerIsAttendee(self):
""" iCal organizer is attendee - bug #1839 """
# This tries to have the same behavior as iCal
# 1. create an event, add an attendee and add the organizer as an attendee
# 2. SOGo should remove the organizer from the attendee list
ics_name = "test-organizer-is-attendee.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar, ics_name), None)
self._deleteEvent(self.attendee1_client,
"%s%s" % (self.attendee1_calendar, ics_name), None)
# 1. create a recurring event in the organiser's calendar
summary="org is attendee"
uid=summary
event = self._newEvent(summary, uid)
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.partstat_param = "ACCEPTED"
organizer.value = self.user_email
attendee = event.vevent.add('attendee')
attendee.cn_param = self.attendee1_name
attendee.rsvp_param = "TRUE"
attendee.role_param = "REQ-PARTICIPANT"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.attendee1_email
# 1.1 add the organizer as an attendee
attendee = event.vevent.add('attendee')
attendee.cn_param = self.user_name
attendee.rsvp_param = "TRUE"
attendee.role_param = "REQ-PARTICIPANT"
attendee.partstat_param = "ACCEPTED"
attendee.value = self.user_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# 2. Fetch the event and make sure the organizer is not in the attendee list anymore
org_ev = self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
for attendee in org_ev.vevent.attendee_list:
self.assertNotEqual(self.user_email, attendee.value)
def testEventsWithSameUID(self):
""" PUT 2 events with the same UID - bug #1853 """
ics_name = "test-same-uid.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar, ics_name), None)
conflict_ics_name = "test-same-uid-conflict.ics"
self.ics_list += [ics_name]
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar, conflict_ics_name), None)
# 1. create simple event
summary="same uid"
uid=summary
event = self._newEvent(summary, uid)
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
# PUT the same event with a new filename - should trigger a 403
self._putEvent(self.client, "%s%s" % (self.user_calendar, conflict_ics_name), event, exp_status=403)
def testInvitationDelegation(self): def testInvitationDelegation(self):
""" invitation delegation """ """ invitation delegation """
ics_name = "test-delegation.ics"
self.ics_list += [ics_name]
# the invitation must not exist # the invitation must not exist
self._deleteEvent(self.client, self._deleteEvent(self.client,
"%stest-delegation.ics" % self.user_calendar, None) "%s%s" % (self.user_calendar, ics_name), None)
self._deleteEvent(self.attendee1_client, self._deleteEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar, None) "%s%s" % (self.attendee1_calendar, ics_name), None)
self._deleteEvent(self.attendee1_delegate_client, self._deleteEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar, "%s%s" % (self.attendee1_delegate_calendar, ics_name), None)
None)
# 1. org -> attendee => org: 1, attendee: 1 (pst=N-A), delegate: 0 # 1. org -> attendee => org: 1, attendee: 1 (pst=N-A), delegate: 0

View File

@ -109,7 +109,7 @@
return rc=NO; return rc=NO;
} }
sql = [NSString stringWithFormat: @"SELECT count(*) FROM %@ WHERE c_lastseen <= %d;", sql = [NSString stringWithFormat: @"SELECT count(*) FROM %@ WHERE c_lastseen <= %d",
[tableURL gcsTableName], oldest]; [tableURL gcsTableName], oldest];
ex = [channel evaluateExpressionX: sql]; ex = [channel evaluateExpressionX: sql];
if (ex) if (ex)
@ -129,7 +129,7 @@
if (verbose) if (verbose)
NSLog(@"Will be removing %d sessions", sessionsToDelete); NSLog(@"Will be removing %d sessions", sessionsToDelete);
[channel cancelFetch]; [channel cancelFetch];
sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE c_lastseen <= %d;", sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE c_lastseen <= %d",
[tableURL gcsTableName], oldest]; [tableURL gcsTableName], oldest];
if (verbose) if (verbose)
NSLog(@"Removing sessions older than %d minute(s)", nbMinutes); NSLog(@"Removing sessions older than %d minute(s)", nbMinutes);

View File

@ -1,15 +1,16 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2005-2012 Inverse inc.
This file is part of OpenGroupware.org. This file is part of SOGo.
OGo is free software; you can redistribute it and/or modify it under SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.
@ -590,4 +591,9 @@
); );
} }
- (int) minimumSearchLength
{
return [[[context activeUser] domainDefaults] searchMinimumWordLength];
}
@end /* UIxPageFrame */ @end /* UIxPageFrame */

View File

@ -1,14 +1,15 @@
/* /*
Copyright (C) 2000-2005 SKYRIX Software AG Copyright (C) 2000-2005 SKYRIX Software AG
Copyright (C) 2000-2012 Inverse inc.
This file is part of OpenGroupware.org. This file is part of SOGo.
OGo is free software; you can redistribute it and/or modify it under SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.

View File

@ -1,6 +1,6 @@
/* UIxContactsUserFolders.m - this file is part of SOGo /* UIxContactsUserFolders.m - this file is part of SOGo
* *
* Copyright (C) 2007-2010 Inverse inc. * Copyright (C) 2007-2012 Inverse inc.
* *
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *

View File

@ -1,6 +1,6 @@
/* UIxListEditor.m - this file is part of SOGo /* UIxListEditor.m - this file is part of SOGo
* *
* Copyright (C) 2008-2011 Inverse inc. * Copyright (C) 2008-2012 Inverse inc.
* *
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Francis Lachapelle <flachapelle@inverse.ca> * Francis Lachapelle <flachapelle@inverse.ca>

View File

@ -11,7 +11,7 @@
"Reply" = "Odpovědět"; "Reply" = "Odpovědět";
"Reply All" = "Odp. všem"; "Reply All" = "Odp. všem";
"Print" = "Tisk"; "Print" = "Tisk";
"Stop" = "Stop"; "Stop" = "Zastavit";
"Write" = "Napsat"; "Write" = "Napsat";
"Send" = "Odeslat"; "Send" = "Odeslat";
@ -19,6 +19,7 @@
"Attach" = "Přiložit"; "Attach" = "Přiložit";
"Save" = "Uložit"; "Save" = "Uložit";
"Options" = "Možnosti"; "Options" = "Možnosti";
"Close" = "Zavřít";
"Size" = "Velikost"; "Size" = "Velikost";
/* Tooltips */ /* Tooltips */
@ -63,8 +64,10 @@
"Shared Account: " = "Sdílený účet: "; "Shared Account: " = "Sdílený účet: ";
/* acls */ /* acls */
"Default Roles" = "Výchozí oprávnění"; "Access rights to" = "Přístupová práva k";
"User rights for:" = "Uživatelská práva pro:"; "For user" = "Pro uživatele";
"Any Authenticated User" = "Všichni ověření uživatelé";
"List and see this folder" = "Prohlížet tuto složku"; "List and see this folder" = "Prohlížet tuto složku";
"Read mails from this folder" = "Číst maily v této složce"; "Read mails from this folder" = "Číst maily v této složce";
@ -104,15 +107,13 @@
"cc" = "Kopie"; "cc" = "Kopie";
"bcc" = "Skrytá kopie"; "bcc" = "Skrytá kopie";
"Addressbook" = "Adresář";
"Edit Draft..." = "Upravit koncept..."; "Edit Draft..." = "Upravit koncept...";
"Load Images" = "Nahrát obrázky"; "Load Images" = "Nahrát obrázky";
"Return Receipt" = "Return Receipt"; "Return Receipt" = "Potvrzení o přečtení";
"The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?" = "The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?"; "The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?" = "Odesílatel této zprávy si přeje být informován o tom, že jste si tuto zprávu přečetli. Chcete odesílateli poslat potvrzení?";
"Return Receipt (displayed) - %@"= "Return Receipt (displayed) - %@"; "Return Receipt (displayed) - %@"= "Potvrzení o přečtení (zobrazeno) - %@";
"This is a Return Receipt for the mail that you sent to %@.\n\nNote: This Return Receipt only acknowledges that the message was displayed on the recipient's computer. There is no guarantee that the recipient has read or understood the message contents." = "This is a Return Receipt for the mail that you sent to %@.\n\nNote: This Return Receipt only acknowledges that the message was displayed on the recipient's computer. There is no guarantee that the recipient has read or understood the message contents."; "This is a Return Receipt for the mail that you sent to %@.\n\nNote: This Return Receipt only acknowledges that the message was displayed on the recipient's computer. There is no guarantee that the recipient has read or understood the message contents." = "Toto je potvrzení o přečtení ke zprávě, kterou jste poslali pro %@.\n\nPoznámka: Potvrzení o přijetí znamená pouze to, že se zpráva zobrazila na počítači adresáta. Není ale zaručeno, že adresát zprávu četl a porozuměl jejímu obsahu.";
"Priority" = "Priorita"; "Priority" = "Priorita";
"highest" = "Nejvyšší"; "highest" = "Nejvyšší";
@ -123,6 +124,9 @@
"This mail is being sent from an unsecure network!" = "Tento mail je odesílán z nezabezpečené sítě!"; "This mail is being sent from an unsecure network!" = "Tento mail je odesílán z nezabezpečené sítě!";
"Address Book:" = "Adresář:";
"Search For:" = "Hledat:";
/* Popup "show" */ /* Popup "show" */
"all" = "všechny"; "all" = "všechny";
@ -141,8 +145,7 @@
"Date" = "Datum"; "Date" = "Datum";
"View" = "Zobrazit"; "View" = "Zobrazit";
"All" = "Všechny"; "All" = "Všechny";
"Unread" = "Nepřečtené"; "No message" = "Žádná zpráva";
"No message" = "No message";
"messages" = "zprávy"; "messages" = "zprávy";
"first" = "Nejnovější"; "first" = "Nejnovější";
@ -196,8 +199,8 @@
"Delete Folder" = "Smazat složku"; "Delete Folder" = "Smazat složku";
"Use This Folder For" = "Použít tuto složku pro"; "Use This Folder For" = "Použít tuto složku pro";
"Get Messages for Account" = "Stáhnout zprávy pro účet"; "Get Messages for Account" = "Stáhnout zprávy pro účet";
"Properties..." = "Properties..."; "Properties..." = "Vlastnosti...";
"Delegation..." = "Delegation..."; "Delegation..." = "Delegoní...";
/* Use This Folder menu */ /* Use This Folder menu */
"Sent Messages" = "Odeslané zprávy"; "Sent Messages" = "Odeslané zprávy";
@ -208,7 +211,6 @@
"Open Message In New Window" = "Otevřít zprávu v novém okně"; "Open Message In New Window" = "Otevřít zprávu v novém okně";
"Reply to Sender Only" = "Odpovědět pouze odesílateli"; "Reply to Sender Only" = "Odpovědět pouze odesílateli";
"Reply to All" = "Odpovědět všem"; "Reply to All" = "Odpovědět všem";
"Forward" = "Přeposlat";
"Edit As New..." = "Upravit jako novou..."; "Edit As New..." = "Upravit jako novou...";
"Move To" = "Přesunout do"; "Move To" = "Přesunout do";
"Copy To" = "Kopírovat do"; "Copy To" = "Kopírovat do";
@ -255,11 +257,11 @@
"Please select a message." = "Vyberte zprávu prosím."; "Please select a message." = "Vyberte zprávu prosím.";
"Please select a message to print." = "Zvolte prosím zprávu, kterou chcete tisknout."; "Please select a message to print." = "Zvolte prosím zprávu, kterou chcete tisknout.";
"Please select only one message to print." = "Zvolte pouze jednu zprávu, kterou chcete tisknout."; "Please select only one message to print." = "Zvolte pouze jednu zprávu, kterou chcete tisknout.";
"The message you have selected doesn't exist anymore." = "The message you have selected doesn't exist anymore."; "The message you have selected doesn't exist anymore." = "Zpráva, kterou jste zvolili, již neexistuje.";
"The folder with name \"%{0}\" could not be created." "The folder with name \"%{0}\" could not be created."
= "Složka s názvem \"%{0}\" nemohla být vytvořen."; = "Složka s názvem \"%{0}\" nemohla být vytvořena.";
"This folder could not be renamed to \"%{0}\"." "This folder could not be renamed to \"%{0}\"."
= "Tato složka nemohla být přejmenována na \"%{0}\"."; = "Tato složka nemohla být přejmenována na \"%{0}\".";
"The folder could not be deleted." "The folder could not be deleted."
@ -272,20 +274,20 @@
"You need to choose a non-virtual folder!" = "Musíte zvolit ne-virtuální složku!"; "You need to choose a non-virtual folder!" = "Musíte zvolit ne-virtuální složku!";
"Moving a message into its own folder is impossible!" "Moving a message into its own folder is impossible!"
= "Je nemožné přesunout zprávu do své vlastní složky!"; = "Zprávu nelze přesunout do své vlastní složky!";
"Copying a message into its own folder is impossible!" "Copying a message into its own folder is impossible!"
= "Je nemožné zkopírovat zprávu do své vlastní složky!"; = "Zprávu nelze zkopírovat do své vlastní složky!";
/* Message operations */ /* Message operations */
"The messages could not be moved to the trash folder. Would you like to delete them immediately?" "The messages could not be moved to the trash folder. Would you like to delete them immediately?"
= "The messages could not be moved to the trash folder. Would you like to delete them immediately?"; = "Zprávy nemohou být přesunuty do koše. Chcete je smazat trvale?";
/* Message editing */ /* Message editing */
"error_validationfailed" = "Potvrzení selhalo";
"error_missingsubject" = "Chybí předmět"; "error_missingsubject" = "Chybí předmět";
"error_missingrecipients" = "Příjemci nebyli specifikováni"; "error_missingrecipients" = "Příjemci nebyli specifikováni";
"Send Anyway" = "Odeslat";
/* Message sending */ /* Message sending */
"cannot send message: (smtp) all recipients discarded" = "Cannot send message: all recipients are invalid."; "cannot send message: (smtp) all recipients discarded" = "Zprávu nelze odeslat: adresy všech příjemců jsou neplatné.";
"cannot send message (smtp) - recipients discarded:" = "Cannot send message. The following addresses are invalid:"; "cannot send message (smtp) - recipients discarded:" = "Zprávu nelze odeslat: následující adresy jsou neplatné:";
"cannot send message: (smtp) error when connecting" = "Cannot send message: error when connecting to the SMTP server."; "cannot send message: (smtp) error when connecting" = "Zprávu nelze odeslat: při spojení se SMTP serverem došlo k chybě.";

View File

@ -1,6 +1,6 @@
/* UIxAttendeesEditor.h - this file is part of SOGo /* UIxAttendeesEditor.h - this file is part of SOGo
* *
* Copyright (C) 2007 Inverse inc. * Copyright (C) 2007-2012 Inverse inc.
* *
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
@ -28,7 +28,6 @@
@interface UIxAttendeesEditor : UIxComponent @interface UIxAttendeesEditor : UIxComponent
{ {
NSString *item; NSString *item;
// NSString *zoom;
} }
- (void) setItem: (NSString *) newItem; - (void) setItem: (NSString *) newItem;

View File

@ -1,6 +1,6 @@
/* UIxAttendeesEditor.m - this file is part of SOGo /* UIxAttendeesEditor.m - this file is part of SOGo
* *
* Copyright (C) 2007 Inverse inc. * Copyright (C) 2007-2012 Inverse inc.
* *
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *

View File

@ -130,6 +130,7 @@
<script type="text/javascript"> <script type="text/javascript">
var ApplicationBaseURL = '<var:string value="modulePath"/>'; var ApplicationBaseURL = '<var:string value="modulePath"/>';
var ResourcesURL = '<var:string value="applicationPath"/>.woa/WebServerResources'; var ResourcesURL = '<var:string value="applicationPath"/>.woa/WebServerResources';
var minimumSearchLength = <var:string value="minimumSearchLength"/>;
<var:if condition="isSuperUser" <var:if condition="isSuperUser"
>var IsSuperUser = true; >var IsSuperUser = true;
</var:if> </var:if>

View File

@ -125,7 +125,7 @@ var SOGoAutoCompletionInterface = {
document.contactLookupAjaxRequest.aborted = true; document.contactLookupAjaxRequest.aborted = true;
document.contactLookupAjaxRequest.abort(); document.contactLookupAjaxRequest.abort();
} }
if (input.value.trim().length > 2) { if (input.value.trim().length > minimumSearchLength) {
var urlstr = UserFolderURL + "Contacts/"; var urlstr = UserFolderURL + "Contacts/";
if (input.addressBook) if (input.addressBook)
urlstr += input.addressBook + "/contact"; urlstr += input.addressBook + "/contact";

View File

@ -177,7 +177,7 @@ function onContactKeydown(event) {
function performSearch(input) { function performSearch(input) {
// Perform address completion // Perform address completion
if (!input.value.blank()) { if (input.value.trim().length > minimumSearchLength) {
var urlstr = (UserFolderURL var urlstr = (UserFolderURL
+ "Contacts/allContactSearch?excludeGroups=1&search=" + "Contacts/allContactSearch?excludeGroups=1&search="
+ encodeURIComponent(input.value)); + encodeURIComponent(input.value));

View File

@ -1,8 +1,6 @@
var d; var d;
function onSearchFormSubmit() { function onSearchFormSubmit() {
startAnimation($("pageContent"), $("filterPanel"));
var searchValue = $("searchValue"); var searchValue = $("searchValue");
var encodedValue = encodeURI(searchValue.value); var encodedValue = encodeURI(searchValue.value);
@ -16,8 +14,11 @@ function onSearchFormSubmit() {
document.userFoldersRequest.aborted = true; document.userFoldersRequest.aborted = true;
document.userFoldersRequest.abort(); document.userFoldersRequest.abort();
} }
document.userFoldersRequest if (encodedValue.trim().length > minimumSearchLength) {
= triggerAjaxRequest(url, usersSearchCallback); startAnimation($("pageContent"), $("filterPanel"));
document.userFoldersRequest
= triggerAjaxRequest(url, usersSearchCallback);
}
} }
return false; return false;

View File

@ -1,7 +1,7 @@
/* generic.js - this file is part of SOGo /* generic.js - this file is part of SOGo
Copyright (C) 2005 SKYRIX Software AG Copyright (C) 2005 SKYRIX Software AG
Copyright (C) 2006-2011 Inverse Copyright (C) 2006-2012 Inverse
SOGo is free software; you can redistribute it and/or modify it under SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the the terms of the GNU Lesser General Public License as published by the
@ -1143,7 +1143,7 @@ function onSearchFormSubmit(event) {
if (searchValue.value != searchValue.ghostPhrase if (searchValue.value != searchValue.ghostPhrase
&& (searchValue.value != searchValue.lastSearch && (searchValue.value != searchValue.lastSearch
|| searchValue.value.strip().length > 0)) { && searchValue.value.strip().length > minimumSearchLength)) {
search["criteria"] = searchCriteria.value; search["criteria"] = searchCriteria.value;
search["value"] = searchValue.value; search["value"] = searchValue.value;
searchValue.lastSearch = searchValue.value; searchValue.lastSearch = searchValue.value;

View File

@ -15,7 +15,7 @@ Group: Productivity/Groupware
Source: SOGo-%{sogo_version}.tar.gz Source: SOGo-%{sogo_version}.tar.gz
Prefix: /usr Prefix: /usr
AutoReqProv: off AutoReqProv: off
Requires: gnustep-base >= 1.23, sope%{sope_major_version}%{sope_minor_version}-core, httpd, sope%{sope_major_version}%{sope_minor_version}-core, sope%{sope_major_version}%{sope_minor_version}-appserver, sope%{sope_major_version}%{sope_minor_version}-ldap, sope%{sope_major_version}%{sope_minor_version}-cards >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-gdl1-contentstore >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-sbjson, memcached, libmemcached Requires: gnustep-base >= 1.23, sope%{sope_major_version}%{sope_minor_version}-core, httpd, sope%{sope_major_version}%{sope_minor_version}-core, sope%{sope_major_version}%{sope_minor_version}-appserver, sope%{sope_major_version}%{sope_minor_version}-ldap, sope%{sope_major_version}%{sope_minor_version}-cards >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-gdl1-contentstore >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-sbjson, libmemcached
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}
BuildRequires: gcc-objc gnustep-base gnustep-make sope%{sope_major_version}%{sope_minor_version}-appserver-devel sope%{sope_major_version}%{sope_minor_version}-core-devel sope%{sope_major_version}%{sope_minor_version}-ldap-devel sope%{sope_major_version}%{sope_minor_version}-mime-devel sope%{sope_major_version}%{sope_minor_version}-xml-devel sope%{sope_major_version}%{sope_minor_version}-gdl1-devel sope%{sope_major_version}%{sope_minor_version}-sbjson-devel libmemcached-devel %{?oc_build_depends} BuildRequires: gcc-objc gnustep-base gnustep-make sope%{sope_major_version}%{sope_minor_version}-appserver-devel sope%{sope_major_version}%{sope_minor_version}-core-devel sope%{sope_major_version}%{sope_minor_version}-ldap-devel sope%{sope_major_version}%{sope_minor_version}-mime-devel sope%{sope_major_version}%{sope_minor_version}-xml-devel sope%{sope_major_version}%{sope_minor_version}-gdl1-devel sope%{sope_major_version}%{sope_minor_version}-sbjson-devel libmemcached-devel %{?oc_build_depends}