diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 82c1868fc..0d5287a8a 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -457,13 +457,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSData *d; // - // We mark the cache object as deleted + // We destroy the cache object // key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], [folderToDelete nameInContainer]]; o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; [o setTableUrl: [self folderTableURL]]; - [o reloadIfNeeded]; - [o delete]; + [o destroy]; // // We update the FolderSync's synckey @@ -697,7 +696,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (![imapGUIDs allKeysForObject: cKey]) { - // Delete folders cache content to avoid stale data if a new folder gets created with the same name + // Destroy folders cache content to avoid stale data if a new folder gets created with the same name key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: cKey]]; o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; [o setObjectType: ActiveSyncFolderCacheObject]; @@ -711,8 +710,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. command_count++; } - [[o properties] removeAllObjects]; - [o save]; + [o destroy]; } } diff --git a/ChangeLog b/ChangeLog index 7c75ed5a7..7765b3388 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,637 @@ +commit 397a36cc89f0160bc84ba19b2f7adc2b96a6a5c1 +Author: Francis Lachapelle +Date: Wed Jul 30 11:16:10 2014 -0400 + + Preparation for release 2.2.7 + +M Documentation/SOGo Installation Guide.odt +M Documentation/SOGo Mozilla Thunderbird Configuration.odt +M Documentation/SOGo Native Microsoft Outlook Configuration.odt +M Version + +commit c240931c54873943349d0c65c1fb9b1dadcbd9c1 +Author: Francis Lachapelle +Date: Wed Jul 30 11:15:05 2014 -0400 + + Update CKEditor to version 4.4.3 + +M NEWS +M UI/WebServerResources/ckeditor/build-config.js +M UI/WebServerResources/ckeditor/ckeditor.js +M UI/WebServerResources/ckeditor/lang/ar.js +M UI/WebServerResources/ckeditor/lang/ca.js +M UI/WebServerResources/ckeditor/lang/cs.js +M UI/WebServerResources/ckeditor/lang/cy.js +M UI/WebServerResources/ckeditor/lang/da.js +M UI/WebServerResources/ckeditor/lang/de.js +M UI/WebServerResources/ckeditor/lang/en.js +M UI/WebServerResources/ckeditor/lang/es.js +M UI/WebServerResources/ckeditor/lang/fi.js +M UI/WebServerResources/ckeditor/lang/fr.js +M UI/WebServerResources/ckeditor/lang/hu.js +M UI/WebServerResources/ckeditor/lang/is.js +M UI/WebServerResources/ckeditor/lang/it.js +M UI/WebServerResources/ckeditor/lang/nb.js +M UI/WebServerResources/ckeditor/lang/nl.js +M UI/WebServerResources/ckeditor/lang/no.js +M UI/WebServerResources/ckeditor/lang/pl.js +M UI/WebServerResources/ckeditor/lang/pt-br.js +M UI/WebServerResources/ckeditor/lang/ru.js +M UI/WebServerResources/ckeditor/lang/sk.js +M UI/WebServerResources/ckeditor/lang/sv.js +M UI/WebServerResources/ckeditor/lang/uk.js +M UI/WebServerResources/ckeditor/plugins/colordialog/dialogs/colordialog.js +M UI/WebServerResources/ckeditor/plugins/image/dialogs/image.js +M UI/WebServerResources/ckeditor/plugins/tabletools/dialogs/tableCell.js +M UI/WebServerResources/ckeditor/skins/moono/dialog.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie7.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie8.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_iequirks.css +M UI/WebServerResources/ckeditor/skins/moono/editor.css +M UI/WebServerResources/ckeditor/skins/moono/editor_gecko.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie7.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie8.css +M UI/WebServerResources/ckeditor/skins/moono/editor_iequirks.css + +commit ef2432e10f396886f09134ff39c9b90cac5e8c2b +Author: Francis Lachapelle +Date: Wed Jul 30 10:36:31 2014 -0400 + + Update NEWS file + +M NEWS + +commit 571322958a875e53dc0c689caf3f24a1debdea54 +Author: Francis Lachapelle +Date: Wed Jul 30 10:33:15 2014 -0400 + + Update fi, fr, de, and es_ES translations + +M SoObjects/Appointments/Finnish.lproj/Localizable.strings +M SoObjects/Appointments/French.lproj/Localizable.strings +M SoObjects/Appointments/German.lproj/Localizable.strings +M SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings +M UI/Common/German.lproj/Localizable.strings +M UI/PreferencesUI/Finnish.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/German.lproj/Localizable.strings +M UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings +M UI/Scheduler/Finnish.lproj/Localizable.strings +M UI/Scheduler/French.lproj/Localizable.strings +M UI/Scheduler/German.lproj/Localizable.strings +M UI/Scheduler/SpanishSpain.lproj/Localizable.strings + +commit bd5c05cca2aa9166198c9c6ea6e9c583da9359bd +Author: Francis Lachapelle +Date: Wed Jul 30 10:31:25 2014 -0400 + + Indentation in JavaScript files + +M UI/WebServerResources/AdministrationUI.js +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/MailerUI.js +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/UIxPreferences.js +M UI/WebServerResources/generic.js + +commit ea44308aa3d95d150b6d6565cdf4dbdc79e5c64e +Author: Francis Lachapelle +Date: Tue Jul 29 16:05:46 2014 -0400 + + Fix all-day events display in IE + + It also fixes the display of the event categories for all-day events in + IE. + +M NEWS +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js + +commit 94f3246658c151b55ca86404c607fe29de7b94b1 +Author: Ludovic Marcotte +Date: Tue Jul 29 10:36:20 2014 -0400 + + Improved error message when you cannot invite users + +M SoObjects/Appointments/English.lproj/Localizable.strings +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit 1df7e989e012fbbb3a93293ed944b2c80f48299f +Author: Ludovic Marcotte +Date: Fri Jul 25 14:48:25 2014 -0400 + + Removed worthless declaration + +M UI/Scheduler/UIxCalListingActions.m + +commit d9ca1051b31f7efd35083dc87bd59b143cb3e593 +Author: Francis Lachapelle +Date: Fri Jul 25 11:34:33 2014 -0400 + + Improve headers of multicolumns view + +M UI/Templates/SchedulerUI/UIxCalDayTable.wox +M UI/Templates/SchedulerUI/UIxCalendarSelector.wox +M UI/WebServerResources/SchedulerUI.css + +commit 4eefa4c2697d4da8ba72674a81f74492e01a11dd +Author: Ludovic Marcotte +Date: Fri Jul 25 08:12:12 2014 -0400 + + Fixed test since we now return a 200 instead of a 204 + +M Tests/Integration/utilities.py + +commit 4c21a050d91e5969921db8f9971752863a7edd48 +Author: Ludovic Marcotte +Date: Thu Jul 24 14:00:57 2014 -0400 + + JSON interface to folder subscriptions + +M NEWS +M UI/MainUI/SOGoUserHomePage.m +M UI/WebServerResources/UIxContactsUserFolders.js + +commit b52289904f607121627a5cf3298f7124a5042d22 +Author: Alexandre Cloutier +Date: Thu Jul 24 13:48:41 2014 -0400 + + task.png for DnD taskList + +A UI/WebServerResources/task.png + +commit c389630d76b4d72e10552be32f8482b4807c1558 +Author: Alexandre Cloutier +Date: Thu Jul 24 13:35:14 2014 -0400 + + DnD tasksList + +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js + +commit f6ef94a6314e3fe7d5b863d20a0df4580e8ca7bb +Author: Ludovic Marcotte +Date: Thu Jul 24 12:48:03 2014 -0400 + + improved handling of SOGoSubscriptionFolderFormat + +M NEWS +M SoObjects/Appointments/SOGoAppointmentFolders.m +M SoObjects/SOGo/SOGoGCSFolder.m +M SoObjects/SOGo/SOGoUserFolder.m +M UI/Common/UIxAclEditor.m +M UI/Common/UIxFolderActions.m +M UI/MainUI/SOGoUserHomePage.m +M UI/WebServerResources/UIxContactsUserFolders.js + +commit 5de51e8aa1facd6ddaf1162f31f9e42ca810d125 +Author: Francis Lachapelle +Date: Thu Jul 24 09:30:19 2014 -0400 + + Fix missing argument with new DnD controller + +M UI/WebServerResources/SchedulerUIDnD.js + +commit 006727a74b42ee0fe5fcb479a9ab8080db14602b +Author: Ludovic Marcotte +Date: Thu Jul 24 08:39:41 2014 -0400 + + Properly destroy cache objects of vanished folders + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS +M SoObjects/SOGo/SOGoCacheGCSObject.m +M SoObjects/SOGo/SOGoCacheObject.h + +commit 6619840f80dab04eacac6a7fe66436443796bfd8 +Author: Jeroen Dekkers +Date: Tue Jul 22 19:54:18 2014 +0200 + + Add Mailer and Appointments to product.list requires of MailPartViewers + +M UI/MailPartViewers/product.plist + +commit 08fd2fcfaa8488a8460c0b555b45f4950f1010a2 +Author: Francis Lachapelle +Date: Tue Jul 22 13:26:28 2014 -0400 + + Fix JavaScript after bad merge + +M UI/WebServerResources/SchedulerUI.js + +commit a43dd97fc939692f74f38dfcd2064147e8cafbdc +Author: Alexandre Cloutier +Date: Tue Jul 22 11:37:45 2014 -0400 + + Fix bug with DnD; leftPanelController.stopEvent() + +M UI/WebServerResources/SchedulerUIDnD.js + +commit 192659fdd9d64ddd63f8dcd014a14a3b4e7fad15 +Author: Francis Lachapelle +Date: Mon Jul 21 16:12:27 2014 -0400 + + Add missing localizable string + +M UI/PreferencesUI/English.lproj/Localizable.strings + +commit 06fe36fd75aaedd05ff7e92cad1320f4dff63160 +Author: Francis Lachapelle +Date: Mon Jul 21 16:07:13 2014 -0400 + + Add missing localizable strings + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit 24f9d83ff7e8503118aabd02598f3300a07a646b +Author: Francis Lachapelle +Date: Mon Jul 21 16:06:24 2014 -0400 + + Fix JavaScript from merge leftover + +M UI/WebServerResources/SchedulerUI.js + +commit 8dd967d0a87a7ee560b854f667fb55c296b89245 +Author: Ludovic Marcotte +Date: Mon Jul 21 15:38:23 2014 -0400 + + Updated NEWS file for two merged pull requests + +M NEWS + +commit 08cd080d47f4ef0d0d262021e173b5d6c6519276 +Author: Ludovic Marcotte +Date: Mon Jul 21 15:34:56 2014 -0400 + + Fixed charset substitution in meta tags + +M NEWS +M UI/MailPartViewers/UIxMailPartHTMLViewer.m + +commit 28e6bb8b9d521dad8801206655c30d57ef3f9ff6 +Author: Alexandre Cloutier +Date: Fri Jul 18 15:03:28 2014 -0400 + + Applied comments + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Templates/SchedulerUI/UIxCalMainView.wox +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/SchedulerUIDnD.js + +commit b892b3f7946e4466a51af901fb4b77da75872d40 +Author: Alexandre Cloutier +Date: Fri Jul 18 10:14:38 2014 -0400 + + Add RegEx + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/WebServerResources/SchedulerUI.js + +commit cdf4cc1acb98f451bec00ab7d6e3858937c293a8 +Author: Alexandre Cloutier +Date: Fri Jul 18 09:46:31 2014 -0400 + + fix DnD from eventList to calendarList for repeated event + +M UI/WebServerResources/SchedulerUI.js + +commit 3cac794de5dbda0f005901cbb081b2ee5fa01e7f +Author: Alexandre Cloutier +Date: Fri Jul 18 09:33:51 2014 -0400 + + move line + +M UI/WebServerResources/SchedulerUIDnD.js + +commit 8f9b24a23f593eb2041cfaaca3e4fb611ddd7060 +Author: Alexandre Cloutier +Date: Fri Jul 18 09:23:07 2014 -0400 + + visual helper while dragging from calendar view to calendarList + +M UI/Templates/SchedulerUI/UIxCalMainView.wox +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUIDnD.js + +commit a2a7014140ce8abf97ce9b04b9eb7467deda192b +Author: Alexandre Cloutier +Date: Thu Jul 17 09:42:32 2014 -0400 + + fix dnd repeated event bug + applied comments + +M UI/Scheduler/UIxAppointmentActions.m +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/SchedulerUIDnD.js + +commit a5ecb2063f449505ab671e3952497f4484b12bec +Author: Alexandre Cloutier +Date: Wed Jul 16 10:30:48 2014 -0400 + + added event7.png and adjust javascript indentation + +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/SchedulerUIDnD.js +A UI/WebServerResources/event7.png + +commit 9bd7aeebfb5ea41c078c7e9691fe0c0e9513eb12 +Author: Alexandre Cloutier +Date: Tue Jul 15 17:29:39 2014 -0400 + + DnD from calendarView to CalendarList + +M UI/WebServerResources/SchedulerUIDnD.js + +commit 01af72cfe95e1970fa84405732690355ed72dcd8 +Author: Alexandre Cloutier +Date: Tue Jul 15 09:49:48 2014 -0400 + + DnD from eventList to the calendarList + +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js + +commit 6dddb5c98ce402268205eaa4fc7eec3068380f9f +Author: Alexandre Cloutier +Date: Fri Jul 11 17:04:06 2014 -0400 + + dragNdrop from the events table to the calendars list + +M UI/Templates/SchedulerUI/UIxCalMainView.wox +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/UIxOccurenceDialog.js + +commit ccf181bcf95a8bebf6c4ab9bdb66c151548cc00d +Author: Alexandre Cloutier +Date: Thu Jul 10 13:34:47 2014 -0400 + + applied comments and fix repeated events dragNdrop between calendars + +M UI/Scheduler/UIxAppointmentActions.m +M UI/WebServerResources/SchedulerUI.js + +commit e478b29c2e40963041abf63c678a7e36d287ad2f +Author: Alexandre Cloutier +Date: Wed Jul 9 16:47:13 2014 -0400 + + creating and dragNDrop between calendars in multicolumndayview + +M UI/Scheduler/UIxAppointmentActions.m +M UI/Scheduler/UIxCalDayTable.m +M UI/Templates/SchedulerUI/UIxCalDayTable.wox +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/SchedulerUIDnD.js + +commit c2027f072d0b59944882ca39fc97f573a2157fd7 +Author: Alexandre Cloutier +Date: Fri Jul 18 13:57:18 2014 -0400 + + indentation + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit ce6a31dd38aa50736eae48eb073deb991b87173a +Author: Alexandre Cloutier +Date: Fri Jul 18 09:26:02 2014 -0400 + + Adjust line of code to make it easier to read + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit a71013eebf8e7dd088a55cd18554d4de9c69369b +Author: Alexandre Cloutier +Date: Wed Jul 16 14:26:30 2014 -0400 + + javascript indentation + +M UI/WebServerResources/UIxPreferences.js + +commit eaec5a614d713d397b032f58f6d56ac3034165c6 +Author: Alexandre Cloutier +Date: Wed Jul 16 14:08:39 2014 -0400 + + Change exception 403 to 409 and fix indentation in the javascript + +M SoObjects/Appointments/SOGoAppointmentObject.m +M UI/WebServerResources/UIxPreferences.js + +commit 5217bed5ae3e298adc48a4c2e8c94ca1ab0e4891 +Author: Alexandre Cloutier +Date: Wed Jul 16 14:01:50 2014 -0400 + + Organized the categories and appointments invitations inside a tabContainer + +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/UIxPreferences.css +M UI/WebServerResources/UIxPreferences.js +M UI/WebServerResources/generic.css + +commit 17319cc8dd39c46a3f0c7c48e1ef9d182d06a663 +Author: Alexandre Cloutier +Date: Wed Jul 16 10:37:27 2014 -0400 + + reverted UIxListEditor.wox + +M UI/Templates/ContactsUI/UIxListEditor.wox + +commit 89d116363ca6a27b08b3b3e6812669ebbbd69ea0 +Author: Alexandre Cloutier +Date: Wed Jul 16 10:03:13 2014 -0400 + + replace ampersands with commas + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit 7c3e85f517a56ca66f540a8468b75010a5943f5f +Author: Alexandre Cloutier +Date: Tue Jul 15 17:34:49 2014 -0400 + + applied comments + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit 05fb7ae0c88a1f9b47d636caabaab96bea426309 +Author: Alexandre Cloutier +Date: Thu Jul 10 11:13:07 2014 -0400 + + applied comments + +M SoObjects/Appointments/SOGoAppointmentObject.m +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/UIxPreferences.js + +commit ef20108e3297b5d644044307ba84fd2a6c6fbdbc +Author: Alexandre Cloutier +Date: Wed Jul 9 17:26:41 2014 -0400 + + fix bug where the user could enter any names in the whitelist + +M UI/WebServerResources/UIxPreferences.js + +commit b5c0363400a48610f782c06dd0fdba21cc7cc478 +Author: Alexandre Cloutier +Date: Mon Jul 7 17:42:43 2014 -0400 + + applied comment and refactor code + +M SoObjects/Appointments/SOGoAppointmentObject.m +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit f0b8eb8dfb6d19fdc82fe92a796c9e2b9a9c2aa3 +Author: Alexandre Cloutier +Date: Mon Jul 7 13:38:37 2014 -0400 + + server side code to integrate the whitelist + +M SoObjects/Appointments/English.lproj/Localizable.strings +M SoObjects/Appointments/SOGoAppointmentObject.m +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit 16d02455bdcb5e8d2e2c5b4dc160b294895da82c +Author: Alexandre Cloutier +Date: Mon Jul 7 10:00:16 2014 -0400 + + Serialization of the whiteList + +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/UIxPreferences.js + +commit 8ded5a8aaff1e7b4025b6ffe35e3e36d64ebd687 +Author: Alexandre Cloutier +Date: Fri Jul 4 09:51:41 2014 -0400 + + Prevent Invitations and whitelist GUI + +M SoObjects/Appointments/SOGoAppointmentObject.m +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/ContactsUI/UIxListEditor.wox +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/SOGoAutoCompletion.js +M UI/WebServerResources/UIxPreferences.css +M UI/WebServerResources/UIxPreferences.js + +commit 1cc93c700a0925f84262896ae6d28f90395a8b51 +Author: Ludovic Marcotte +Date: Wed Jul 9 14:08:31 2014 -0400 + + Updated NEWS file for changes occurred right after v2.2.6 was released + +M NEWS + +commit 44d0cb5c0d57de6548148ea204f49a5d697e8358 +Author: Francis Lachapelle +Date: Wed Jul 9 11:17:36 2014 -0400 + + Fix refresh of list when forwarding a message + + Fixes #2818 + +M NEWS +M SoObjects/Mailer/SOGoDraftObject.h +M SoObjects/Mailer/SOGoDraftObject.m +M UI/MailerUI/UIxMailEditor.m +M UI/WebServerResources/MailerUI.js +M UI/WebServerResources/UIxMailEditor.js + +commit dbdb7d90513aa54c2aa314013f236c434eeb0082 +Author: Ludovic Marcotte +Date: Wed Jul 9 10:04:44 2014 -0400 + + Fix sending of METHOD:REPLY for DAV clients + +M NEWS +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit e343ed240b8de3b06da82a8777fe5d639cbf6a1c +Author: Francis Lachapelle +Date: Tue Jul 8 22:00:06 2014 -0400 + + Fix regression of word breaking + + Fixes #2845 + +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/generic.css + +commit 45553be122c5dd12b0373e3e6fdc4bd2f1aeef4b +Author: Francis Lachapelle +Date: Tue Jul 8 17:19:55 2014 -0400 + + Fix rename of calendars, improve tasks count badge + +M NEWS +M UI/Scheduler/UIxCalendarSelector.m +M UI/Templates/ContactsUI/UIxContactFoldersView.wox +M UI/Templates/SchedulerUI/UIxCalendarSelector.wox +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/UIxContactFolderProperties.js +M UI/WebServerResources/generic.css +M UI/WebServerResources/generic.js + +commit 1cf4841477dea27261c5654717cabab1ea6ac43e +Author: Alexandre Cloutier +Date: Fri Jul 4 11:23:59 2014 -0400 + + fix display glitch + +M UI/WebServerResources/SchedulerUI.js + +commit 017a85b7f2c91827615ab1adfbaab6dc4fef9707 +Author: Alexandre Cloutier +Date: Fri Jul 4 10:46:49 2014 -0400 + + fix month events categories display + +M UI/WebServerResources/SchedulerUI.js + +commit 8c8e47daf632ce5146df347c407cbdc5ba858681 +Author: Alexandre Cloutier +Date: Fri Jul 4 10:37:35 2014 -0400 + + fix spacing between events and categorie display + +M UI/WebServerResources/SchedulerUI.js + +commit 8a172467253db8e129a0926d949b86410e50d811 +Author: Ludovic Marcotte +Date: Fri Jul 4 10:18:56 2014 -0400 + + Cleaned up code and fixed the translation of the weekdays in the datepicker + +M UI/Scheduler/UIxDatePicker.h +M UI/Scheduler/UIxDatePicker.m +M UI/WebServerResources/datepicker.js + +commit 4843c6b6d7f05e32047a27e1c6cd28a1912e615d +Author: Ludovic Marcotte +Date: Fri Jul 4 08:55:35 2014 -0400 + + Renamed Multi-Column to Multicolumn and refactored some code + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +M UI/Scheduler/UIxCalViewPrint.m + +commit 6e13f1547719c5f3bfc6149d9a038de90c284cb2 +Author: Ludovic Marcotte +Date: Wed Jul 2 11:32:18 2014 -0400 + + Update ChangeLog + +M ChangeLog + commit 5dd2088d42388b397e8cc3fde35455b2455f7f3e Author: Ludovic Marcotte Date: Wed Jul 2 11:32:06 2014 -0400 diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index 81fbe0883..5ec8d2b27 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/Documentation/SOGo Mozilla Thunderbird Configuration.odt b/Documentation/SOGo Mozilla Thunderbird Configuration.odt index 97588c202..ee962b127 100644 Binary files a/Documentation/SOGo Mozilla Thunderbird Configuration.odt and b/Documentation/SOGo Mozilla Thunderbird Configuration.odt differ diff --git a/Documentation/SOGo Native Microsoft Outlook Configuration.odt b/Documentation/SOGo Native Microsoft Outlook Configuration.odt index 7651f52ed..cda523ead 100644 Binary files a/Documentation/SOGo Native Microsoft Outlook Configuration.odt and b/Documentation/SOGo Native Microsoft Outlook Configuration.odt differ diff --git a/NEWS b/NEWS index 4b233c364..3857a5357 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,28 @@ +2.2.7 (2014-07-30) +------------------ + +New features + - new user preference to prevent event invitations + +Enhancements + - improved badges of active tasks count + - refresh draft folder after sending a message + - now possible to DnD events in the calendar list + - improved handling of SOGoSubscriptionFolderFormat + - JSON'ified folder subscription interface + - updated Finnish, French, German, and Spanish (Spain) translations + - updated CKEditor to version 4.4.3 + +Bug fixes + - fixed weekdays translation in the datepicker + - fixed event categories display + - fixed all-day events display in IE + - fixed rename of calendars + - we now correctly add the "METHOD:REPLY" when sending out ITIP messages from DAV clients + - fixed refresh of message headers when forwarding a message (#2818) + - we now correctly escape all charset= in tags, not only in the + - we now destroy cache objects of vanished folders + 2.2.6 (2014-07-02) ------------------ @@ -9,7 +34,7 @@ Enhancements - implemented the GetAttachment ActiveSync command (#2808) - implemented the Ping ActiveSync command - added "soft deletes" support for ActiveSync (#2734) - - now display the active tasks next to calendar names (#2760) + - now display the active tasks count next to calendar names (#2760) Bug fixes - better handling of empty "Flag" messages over ActiveSync (#2806) diff --git a/SoObjects/Appointments/English.lproj/Localizable.strings b/SoObjects/Appointments/English.lproj/Localizable.strings index ba3b942f1..45a038dfa 100644 --- a/SoObjects/Appointments/English.lproj/Localizable.strings +++ b/SoObjects/Appointments/English.lproj/Localizable.strings @@ -1,3 +1,4 @@ +"Inviting the following persons is prohibited:" = "Inviting the following persons is prohibited:"; "Personal Calendar" = "Personal Calendar"; vevent_class0 = "(Public event)"; vevent_class1 = "(Private event)"; diff --git a/SoObjects/Appointments/Finnish.lproj/Localizable.strings b/SoObjects/Appointments/Finnish.lproj/Localizable.strings index f184ddab3..2574c7110 100644 --- a/SoObjects/Appointments/Finnish.lproj/Localizable.strings +++ b/SoObjects/Appointments/Finnish.lproj/Localizable.strings @@ -1,3 +1,4 @@ +"Inviting the following persons is prohibited:" = "Seuraavien henkilöiden kutsuminen on estetty:"; "Personal Calendar" = "Henkilökohtainen kalenteri"; vevent_class0 = "(Julkinen tapahtuma)"; vevent_class1 = "(Yksityinen tapahtuma)"; diff --git a/SoObjects/Appointments/French.lproj/Localizable.strings b/SoObjects/Appointments/French.lproj/Localizable.strings index 7b39d0cf1..690738b77 100644 --- a/SoObjects/Appointments/French.lproj/Localizable.strings +++ b/SoObjects/Appointments/French.lproj/Localizable.strings @@ -1,3 +1,4 @@ +"Inviting the following persons is prohibited:" = "L'invitation des personnes suivantes est interdite :"; "Personal Calendar" = "Agenda personnel"; vevent_class0 = "(Événement public)"; vevent_class1 = "(Événement privé)"; diff --git a/SoObjects/Appointments/German.lproj/Localizable.strings b/SoObjects/Appointments/German.lproj/Localizable.strings index 4cd49d8c5..a9e975288 100644 --- a/SoObjects/Appointments/German.lproj/Localizable.strings +++ b/SoObjects/Appointments/German.lproj/Localizable.strings @@ -1,3 +1,4 @@ +"Inviting the following persons is prohibited:" = "Das Einladen von folgenden Personen ist verboten:"; "Personal Calendar" = "Persönlicher Kalender"; vevent_class0 = "(Öffentlicher Termin)"; vevent_class1 = "(Privater Termin)"; diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index 99b73a636..f5ae1b23c 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -750,7 +750,8 @@ static SoSecurityManager *sm = nil; for (userCount = 0; userCount < userMax; userCount++) [currentFolder subscribeUserOrGroup: [proxySubscribers objectAtIndex: userCount] - reallyDo: YES]; + reallyDo: YES + response: nil]; } } } @@ -775,7 +776,8 @@ static SoSecurityManager *sm = nil; for (userCount = 0; userCount < userMax; userCount++) [currentFolder subscribeUserOrGroup: [proxySubscribers objectAtIndex: userCount] - reallyDo: NO]; + reallyDo: NO + response: nil]; } } } diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 0e397928f..fc578a578 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -25,6 +25,7 @@ #import #import #import +#import #import #import @@ -52,6 +53,7 @@ #import #import #import +#import #import #import #import @@ -126,13 +128,12 @@ } - (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID - { return [[self calendar: NO secure: NO] eventWithRecurrenceID: recID]; } - (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID - forUID: (NSString *) uid + forUID: (NSString *) uid { SOGoAppointmentFolder *folder; SOGoAppointmentObject *object; @@ -146,30 +147,30 @@ while ( object == nil && (folder = [e nextObject]) ) { object = [folder lookupName: nameInContainer - inContext: context - acquire: NO]; + inContext: context + acquire: NO]; if ([object isKindOfClass: [NSException class]] || [object isNew]) - { - possibleName = [folder resourceNameForEventUID: eventUID]; - if (possibleName) - { - object = [folder lookupName: possibleName - inContext: context acquire: NO]; - if ([object isKindOfClass: [NSException class]] || [object isNew]) - object = nil; - } - else - object = nil; - } + { + possibleName = [folder resourceNameForEventUID: eventUID]; + if (possibleName) + { + object = [folder lookupName: possibleName + inContext: context acquire: NO]; + if ([object isKindOfClass: [NSException class]] || [object isNew]) + object = nil; + } + else + object = nil; + } } - + if (!object) { // Create the event in the user's personal calendar. folder = [[SOGoUser userWithLogin: uid] personalCalendarFolderInContext: context]; object = [SOGoAppointmentObject objectWithName: nameInContainer - inContainer: folder]; + inContainer: folder]; [object setIsNew: YES]; } @@ -180,60 +181,60 @@ // // - (void) _addOrUpdateEvent: (iCalEvent *) theEvent - forUID: (NSString *) theUID - owner: (NSString *) theOwner + forUID: (NSString *) theUID + owner: (NSString *) theOwner { if (![theUID isEqualToString: theOwner]) { SOGoAppointmentObject *attendeeObject; NSString *iCalString; - + iCalString = nil; attendeeObject = [self _lookupEvent: [theEvent uid] forUID: theUID]; // We must add an occurence to a non-existing event. We have // to handle this with care, as in the postCalDAVEventRequestTo:from: if ([attendeeObject isNew] && [theEvent recurrenceId]) - { - iCalEvent *ownerEvent; - iCalPerson *person; - SOGoUser *user; - - // We check if the attendee that was added to a single occurence is - // present in the master component. If not, we add it with a participation - // status set to "DECLINED". - ownerEvent = [[[theEvent parent] events] objectAtIndex: 0]; - user = [SOGoUser userWithLogin: theUID]; - if (![ownerEvent userAsAttendee: user]) - { - // Update the master event in the owner's calendar with the - // status of the new attendee set as "DECLINED". + { + iCalEvent *ownerEvent; + iCalPerson *person; + SOGoUser *user; + + // We check if the attendee that was added to a single occurence is + // present in the master component. If not, we add it with a participation + // status set to "DECLINED". + ownerEvent = [[[theEvent parent] events] objectAtIndex: 0]; + user = [SOGoUser userWithLogin: theUID]; + if (![ownerEvent userAsAttendee: user]) + { + // Update the master event in the owner's calendar with the + // status of the new attendee set as "DECLINED". person = [iCalPerson elementWithTag: @"attendee"]; [person setCn: [user cn]]; [person setEmail: [[user allEmails] objectAtIndex: 0]]; [person setParticipationStatus: iCalPersonPartStatDeclined]; [person setRsvp: @"TRUE"]; [person setRole: @"REQ-PARTICIPANT"]; - [ownerEvent addToAttendees: person]; - + [ownerEvent addToAttendees: person]; + iCalString = [[ownerEvent parent] versitString]; - } - } + } + } else - { - // TODO : if [theEvent recurrenceId], only update this occurrence - // in attendee's calendar - - // TODO : when updating the master event, handle exception dates - // in attendee's calendar (add exception dates and remove matching - // occurrences) -- see _updateRecurrenceIDsWithEvent: - - iCalString = [[theEvent parent] versitString]; - } + { + // TODO : if [theEvent recurrenceId], only update this occurrence + // in attendee's calendar + + // TODO : when updating the master event, handle exception dates + // in attendee's calendar (add exception dates and remove matching + // occurrences) -- see _updateRecurrenceIDsWithEvent: + + iCalString = [[theEvent parent] versitString]; + } // Save the event in the attendee's calendar if (iCalString) - [attendeeObject saveContentString: iCalString]; + [attendeeObject saveContentString: iCalString]; } } @@ -242,7 +243,7 @@ // - (void) _removeEventFromUID: (NSString *) theUID owner: (NSString *) theOwner - withRecurrenceId: (NSCalendarDate *) recurrenceId + withRecurrenceId: (NSCalendarDate *) recurrenceId { if (![theUID isEqualToString: theOwner]) { @@ -259,46 +260,46 @@ // Invitations are always written to the personal folder; it's not necessay // to look into all folders of the user folder = [[SOGoUser userWithLogin: theUID] - personalCalendarFolderInContext: context]; + personalCalendarFolderInContext: context]; object = [folder lookupName: nameInContainer inContext: context acquire: NO]; if (![object isKindOfClass: [NSException class]]) - { - if (recurrenceId == nil) - [object delete]; - else - { - calendar = [object calendar: NO secure: NO]; - - // If recurrenceId is defined, remove the occurence from - // the repeating event. - occurences = [calendar events]; - max = [occurences count]; - count = 1; - while (count < max) - { - currentOccurence = [occurences objectAtIndex: count]; - currentId = [currentOccurence recurrenceId]; - if ([currentId compare: recurrenceId] == NSOrderedSame) - { - [[calendar children] removeObject: currentOccurence]; - break; - } - count++; - } - - // Add an date exception. - event = (iCalRepeatableEntityObject*)[calendar firstChildWithTag: [object componentTag]]; - [event addToExceptionDates: recurrenceId]; - - [event increaseSequence]; - - // We generate the updated iCalendar file and we save it - // in the database. - calendarContent = [calendar versitString]; - [object saveContentString: calendarContent]; - } - } + { + if (recurrenceId == nil) + [object delete]; + else + { + calendar = [object calendar: NO secure: NO]; + + // If recurrenceId is defined, remove the occurence from + // the repeating event. + occurences = [calendar events]; + max = [occurences count]; + count = 1; + while (count < max) + { + currentOccurence = [occurences objectAtIndex: count]; + currentId = [currentOccurence recurrenceId]; + if ([currentId compare: recurrenceId] == NSOrderedSame) + { + [[calendar children] removeObject: currentOccurence]; + break; + } + count++; + } + + // Add an date exception. + event = (iCalRepeatableEntityObject*)[calendar firstChildWithTag: [object componentTag]]; + [event addToExceptionDates: recurrenceId]; + + [event increaseSequence]; + + // We generate the updated iCalendar file and we save it + // in the database. + calendarContent = [calendar versitString]; + [object saveContentString: calendarContent]; + } + } } } @@ -317,8 +318,8 @@ { currentUID = [currentAttendee uid]; if (currentUID) - [self _removeEventFromUID: currentUID - owner: owner + [self _removeEventFromUID: currentUID + owner: owner withRecurrenceId: recurrenceId]; } } @@ -343,9 +344,7 @@ [event removeFromAttendees: delegate]; } else - [self errorWithFormat: - @"broken chain: delegate with email '%@' was not found", - mailTo]; + [self errorWithFormat:@"broken chain: delegate with email '%@' was not found", mailTo]; } } @@ -400,9 +399,9 @@ { currentUID = [currentAttendee uid]; if (currentUID) - [self _addOrUpdateEvent: newEvent - forUID: currentUID - owner: owner]; + [self _addOrUpdateEvent: newEvent + forUID: currentUID + owner: owner]; } [self sendEMailUsingTemplateNamed: @"Update" @@ -412,6 +411,73 @@ withType: @"calendar:invitation-update"]; } +// This method scans the list of attendees. +- (NSException *) _handleAttendeeAvailability: (NSArray *) theAttendees + forEvent: (iCalEvent *) theEvent +{ + iCalPerson *currentAttendee; + NSMutableArray *attendees, *unavailableAttendees, *whiteList; + NSEnumerator *enumerator; + NSPredicate *predicate; + NSString *currentUID, *ownerUID; + NSMutableString *reason; + NSDictionary *values; + NSMutableDictionary *value, *moduleSettings; + SOGoUser *user; + SOGoUserSettings *us; + int count = 0, i = 0; + + + // Build list of the attendees uids without ressources + attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]]; + unavailableAttendees = [[NSMutableArray alloc] init]; + enumerator = [theAttendees objectEnumerator]; + ownerUID = [[[self context] activeUser] login]; + + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) + { + user = [SOGoUser userWithLogin: currentUID]; + us = [user userSettings]; + moduleSettings = [us objectForKey:@"Calendar"]; + // Check if the user prevented his account from beeing invited to events + if (![user isResource] && [[moduleSettings objectForKey:@"PreventInvitations"] boolValue]) + { + // Check if the user have a whiteList + whiteList = [NSMutableArray arrayWithObject:[moduleSettings objectForKey:@"PreventInvitationsWhitelist"]]; + predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", ownerUID]; + [whiteList filterUsingPredicate:predicate]; + // If the filter have a hit, do not add the currentUID to the unavailableAttendees array + if ([whiteList count] == 0) + { + values = [NSDictionary dictionaryWithObject:[user cn] forKey:@"Cn"]; + [unavailableAttendees addObject:values]; + } + } + } + } + + count = [unavailableAttendees count]; + if (count > 0) + { + reason = [NSMutableString stringWithString:[self labelForKey: @"Inviting the following persons is prohibited:"]]; + // Add all the unavailable users in the warning message + for (i = 0; i < count; i++) + { + value = [unavailableAttendees objectAtIndex:i]; + [reason appendString:[value keysWithFormat: @"\n %{Cn}"]]; + if (i < count-2) + [reason appendString:@", "]; + } + [unavailableAttendees release]; + return [NSException exceptionWithHTTPStatus:409 reason: reason]; + } + [unavailableAttendees release]; + return nil; +} + // // This methods scans the list of attendees. If they are // considered as resource, it checks for conflicting @@ -435,7 +501,7 @@ NSEnumerator *enumerator; NSString *currentUID; SOGoUser *user, *currentUser, *ownerUser; - + // Build a list of the attendees uids attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]]; enumerator = [theAttendees objectEnumerator]; @@ -447,7 +513,7 @@ [attendees addObject: currentUID]; } } - + // If the active user is not the owner of the calendar, check possible conflict when // the owner is a resource currentUser = [context activeUser]; @@ -455,152 +521,152 @@ { [attendees addObject: owner]; } - + enumerator = [attendees objectEnumerator]; while ((currentUID = [enumerator nextObject])) { - user = [SOGoUser userWithLogin: currentUID]; - - if ([user isResource]) - { - SOGoAppointmentFolder *folder; - NSCalendarDate *start, *end; - NGCalendarDateRange *range; - NSMutableArray *fbInfo; - 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 - // check for at least a year to start with. - start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; - end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; - - folder = [user personalCalendarFolderInContext: context]; - - // Deny access to the resource if the ACLs don't allow the user - if (![folder aclSQLListingFilter]) - { - NSDictionary *values; - NSString *reason; - - values = [NSDictionary dictionaryWithObjectsAndKeys: - [user cn], @"Cn", - [user systemEmail], @"SystemEmail"]; - reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]]; - return [NSException exceptionWithHTTPStatus:403 reason: reason]; - } - - fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start - to: end]]; - - // 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 - // 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--) - { - 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) - { - [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]; - } - } - - // Find the attendee associated to the current UID - for (i = 0; i < [theAttendees count]; i++) + user = [SOGoUser userWithLogin: currentUID]; + + if ([user isResource]) + { + SOGoAppointmentFolder *folder; + NSCalendarDate *start, *end; + NGCalendarDateRange *range; + NSMutableArray *fbInfo; + 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 + // check for at least a year to start with. + start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; + end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; + + folder = [user personalCalendarFolderInContext: context]; + + // Deny access to the resource if the ACLs don't allow the user + if (![folder aclSQLListingFilter]) + { + NSDictionary *values; + NSString *reason; + + values = [NSDictionary dictionaryWithObjectsAndKeys: + [user cn], @"Cn", + [user systemEmail], @"SystemEmail"]; + reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]]; + return [NSException exceptionWithHTTPStatus:403 reason: reason]; + } + + fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start + to: end]]; + + // 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 + // 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--) + { + 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) { - currentAttendee = [theAttendees objectAtIndex: i]; - if ([[currentAttendee uid] isEqualToString: currentUID]) - break; - else - currentAttendee = nil; + [fbInfo removeObjectAtIndex: i]; + continue; } - - if ([fbInfo count]) + + // 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]) { - // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit - // is imposed) or if numberOfSimultaneousBookings is greater than the number of - // overlapping events - if ([user numberOfSimultaneousBookings] == 0 || - [user numberOfSimultaneousBookings] > [fbInfo count]) + must_delete = YES; + + for (j = 0; j < [allOccurences count]; j++) { - if (currentAttendee) + if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]]) { - [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; - [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + must_delete = NO; + break; } } - else - { - iCalCalendar *calendar; - NSDictionary *values; - NSString *reason; - iCalEvent *event; - - calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]]; - event = [[calendar events] lastObject]; - - values = [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", - [user cn], @"Cn", - [user systemEmail], @"SystemEmail", - ([event summary] ? [event summary] : @""), @"EventTitle", - [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate", - nil]; - - reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]]; - - return [NSException exceptionWithHTTPStatus: 403 - reason: reason]; - } - } - else if (currentAttendee) - { - // No conflict, we auto-accept. We do this for resources automatically if no - // double-booking is observed. If it's not the desired behavior, just don't - // set the resource as one! - [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; - [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + + if (must_delete) + [fbInfo removeObjectAtIndex: i]; } } + + // Find the attendee associated to the current UID + for (i = 0; i < [theAttendees count]; i++) + { + currentAttendee = [theAttendees objectAtIndex: i]; + if ([[currentAttendee uid] isEqualToString: currentUID]) + break; + else + currentAttendee = nil; + } + + if ([fbInfo count]) + { + // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit + // is imposed) or if numberOfSimultaneousBookings is greater than the number of + // overlapping events + if ([user numberOfSimultaneousBookings] == 0 || + [user numberOfSimultaneousBookings] > [fbInfo count]) + { + if (currentAttendee) + { + [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + } + } + else + { + iCalCalendar *calendar; + NSDictionary *values; + NSString *reason; + iCalEvent *event; + + calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]]; + event = [[calendar events] lastObject]; + + values = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", + [user cn], @"Cn", + [user systemEmail], @"SystemEmail", + ([event summary] ? [event summary] : @""), @"EventTitle", + [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate", + nil]; + + reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]]; + + return [NSException exceptionWithHTTPStatus: 403 + reason: reason]; + } + } + else if (currentAttendee) + { + // No conflict, we auto-accept. We do this for resources automatically if no + // double-booking is observed. If it's not the desired behavior, just don't + // set the resource as one! + [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + } + } } - + return nil; } @@ -608,27 +674,29 @@ // // - (NSException *) _handleAddedUsers: (NSArray *) attendees - fromEvent: (iCalEvent *) newEvent + fromEvent: (iCalEvent *) newEvent { iCalPerson *currentAttendee; NSEnumerator *enumerator; NSString *currentUID; NSException *e; - + // We check for conflicts if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent])) return e; - + if ((e = [self _handleAttendeeAvailability: attendees forEvent: newEvent])) + return e; + enumerator = [attendees objectEnumerator]; while ((currentAttendee = [enumerator nextObject])) { currentUID = [currentAttendee uid]; if (currentUID) - [self _addOrUpdateEvent: newEvent - forUID: currentUID - owner: owner]; + [self _addOrUpdateEvent: newEvent + forUID: currentUID + owner: owner]; } - + return nil; } @@ -656,12 +724,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { e = [events objectAtIndex: i]; if ([e recurrenceId]) - for (j = 0; j < [theAttendees count]; j++) - if (shouldAdd) - [e addToAttendees: [theAttendees objectAtIndex: j]]; - else - [e removeFromAttendees: [theAttendees objectAtIndex: j]]; - + for (j = 0; j < [theAttendees count]; j++) + if (shouldAdd) + [e addToAttendees: [theAttendees objectAtIndex: j]]; + else + [e removeFromAttendees: [theAttendees objectAtIndex: j]]; } } @@ -709,8 +776,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent withType: @"calendar:cancellation"]; } - if ((ex = [self _handleResourcesConflicts: [newEvent attendees] - forEvent: newEvent])) + if ((ex = [self _handleResourcesConflicts: [newEvent attendees] forEvent: newEvent])) + return ex; + if ((ex = [self _handleAttendeeAvailability: [newEvent attendees] forEvent: newEvent])) return ex; addedAttendees = [changes insertedAttendees]; @@ -719,7 +787,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // the attendees were added to the master event. [self _addOrDeleteAttendees: addedAttendees inRecurrenceExceptionsForEvent: newEvent - add: YES]; + add: YES]; if ([changes sequenceShouldBeIncreased]) { @@ -735,29 +803,29 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // If other attributes have changed, update the event // in each attendee's calendar if ([[changes updatedProperties] count]) - { - NSEnumerator *enumerator; - iCalPerson *currentAttendee; - NSString *currentUID; - + { + NSEnumerator *enumerator; + iCalPerson *currentAttendee; + NSString *currentUID; + updatedAttendees = [newEvent attendees]; - enumerator = [updatedAttendees objectEnumerator]; - while ((currentAttendee = [enumerator nextObject])) - { - currentUID = [currentAttendee uid]; - if (currentUID) - [self _addOrUpdateEvent: newEvent - forUID: currentUID - owner: owner]; - } - } + enumerator = [updatedAttendees objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) + [self _addOrUpdateEvent: newEvent + forUID: currentUID + owner: owner]; + } + } } if ([addedAttendees count]) { // Send an invitation to new attendees if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent])) - return ex; + return ex; [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [newEvent itipEntryWithMethod: @"request"] @@ -765,12 +833,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent toAttendees: addedAttendees withType: @"calendar:invitation"]; } - - [self sendReceiptEmailForObject: newEvent - addedAttendees: addedAttendees - deletedAttendees: deletedAttendees - updatedAttendees: updatedAttendees - operation: EventUpdated]; + + [self sendReceiptEmailForObject: newEvent + addedAttendees: addedAttendees + deletedAttendees: deletedAttendees + updatedAttendees: updatedAttendees + operation: EventUpdated]; return nil; } @@ -783,7 +851,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // +------------> _handleUpdatedEvent:fromOldEvent: ---> _addOrUpdateEvent:forUID:owner: <-----------+ // | | ^ | // v v | | -// _handleRemovedUsers:withRecurrenceId: _handleSequenceUpdateInEvent:ignoringAttendees:fromOldEvent: | +// _handleRemovedUsers:withRecurrenceId: _handleSequenceUpdateInEvent:ignoringAttendees:fromOldEvent: | // | | // | [DELETEAction:] | // | | {_handleAdded/Updated...}<--+ | @@ -822,57 +890,56 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { // New event -- send invitation to all attendees attendees = [newEvent attendeesWithoutUser: ownerUser]; - + // We catch conflicts and abort the save process immediately // in case of one with resources if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent])) return ex; - + if ([attendees count]) - { - [self sendEMailUsingTemplateNamed: @"Invitation" - forObject: [newEvent itipEntryWithMethod: @"request"] - previousObject: nil - toAttendees: attendees + { + [self sendEMailUsingTemplateNamed: @"Invitation" + forObject: [newEvent itipEntryWithMethod: @"request"] + previousObject: nil + toAttendees: attendees withType: @"calendar:invitation"]; - } - + } + [self sendReceiptEmailForObject: newEvent - addedAttendees: attendees - deletedAttendees: nil - updatedAttendees: nil - operation: EventCreated]; + addedAttendees: attendees + deletedAttendees: nil + updatedAttendees: nil + operation: EventCreated]; } - else + else { BOOL hasOrganizer; - + // Event is modified -- sent update status to all attendees // and modify their calendars. recurrenceId = [newEvent recurrenceId]; if (recurrenceId == nil) - oldEvent = [self component: NO secure: NO]; + oldEvent = [self component: NO secure: NO]; else - { - // If recurrenceId is defined, find the specified occurence - // within the repeating vEvent. - recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; - oldEvent = (iCalEvent*)[self lookupOccurrence: recurrenceTime]; - if (oldEvent == nil) - // If no occurence found, create one - oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime]; - } - + { + // If recurrenceId is defined, find the specified occurence + // within the repeating vEvent. + recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; + oldEvent = (iCalEvent*)[self lookupOccurrence: recurrenceTime]; + if (oldEvent == nil) // If no occurence found, create one + oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime]; + } + oldMasterEvent = (iCalEvent *)[[oldEvent parent] firstChildWithTag: [self componentTag]]; hasOrganizer = [[[oldMasterEvent organizer] email] length]; - + if (!hasOrganizer || [oldMasterEvent userIsOrganizer: ownerUser]) - // The owner is the organizer of the event; handle the modifications. We aslo - // catch conflicts just like when the events are created - if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) - return ex; + // The owner is the organizer of the event; handle the modifications. We aslo + // catch conflicts just like when the events are created + if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) + return ex; } - + [super saveComponent: newEvent]; [self flush]; @@ -894,11 +961,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent - (NSException *) _updateAttendee: (iCalPerson *) attendee withDelegate: (iCalPerson *) delegate ownerUser: (SOGoUser *) theOwnerUser - forEventUID: (NSString *) eventUID - withRecurrenceId: (NSCalendarDate *) recurrenceId - withSequence: (NSNumber *) sequence - forUID: (NSString *) uid - shouldAddSentBy: (BOOL) b + forEventUID: (NSString *) eventUID + withRecurrenceId: (NSCalendarDate *) recurrenceId + withSequence: (NSNumber *) sequence + forUID: (NSString *) uid + shouldAddSentBy: (BOOL) b { SOGoAppointmentObject *eventObject; iCalCalendar *calendar; @@ -914,30 +981,30 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent if (![eventObject isNew]) { if (recurrenceId == nil) - { - // We must update main event and all its occurences (if any). - calendar = [eventObject calendar: NO secure: NO]; - event = (iCalEntityObject*)[calendar firstChildWithTag: [self componentTag]]; - } + { + // We must update main event and all its occurences (if any). + calendar = [eventObject calendar: NO secure: NO]; + event = (iCalEntityObject*)[calendar firstChildWithTag: [self componentTag]]; + } else - { - // If recurrenceId is defined, find the specified occurence - // within the repeating vEvent. - recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; - event = [eventObject lookupOccurrence: recurrenceTime]; - - if (event == nil) - // If no occurence found, create one - event = [eventObject newOccurenceWithID: recurrenceTime]; - } + { + // If recurrenceId is defined, find the specified occurence + // within the repeating vEvent. + recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; + event = [eventObject lookupOccurrence: recurrenceTime]; + + if (event == nil) + // If no occurence found, create one + event = [eventObject newOccurenceWithID: recurrenceTime]; + } if ([[event sequence] intValue] <= [sequence intValue]) - { - SOGoUser *currentUser; - - currentUser = [context activeUser]; - otherAttendee = [event userAsAttendee: theOwnerUser]; - + { + SOGoUser *currentUser; + + currentUser = [context activeUser]; + otherAttendee = [event userAsAttendee: theOwnerUser]; + delegateEmail = [otherAttendee delegatedTo]; if ([delegateEmail length]) delegateEmail = [delegateEmail rfc822Email]; @@ -945,7 +1012,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent otherDelegate = [event findAttendeeWithEmail: delegateEmail]; else otherDelegate = NO; - + /* we handle the addition/deletion of delegate users */ addDelegate = NO; removeDelegate = NO; @@ -960,68 +1027,67 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent } } else - addDelegate = YES; + addDelegate = YES; } else { if (otherDelegate) - removeDelegate = YES; + removeDelegate = YES; } - + if (removeDelegate) - { - while (otherDelegate) - { - [event removeFromAttendees: otherDelegate]; - - // Verify if the delegate was already delegate - delegateEmail = [otherDelegate delegatedTo]; - if ([delegateEmail length]) - delegateEmail = [delegateEmail rfc822Email]; - - if ([delegateEmail length]) - otherDelegate = [event findAttendeeWithEmail: delegateEmail]; - else - otherDelegate = NO; - } - } + { + while (otherDelegate) + { + [event removeFromAttendees: otherDelegate]; + + // Verify if the delegate was already delegate + delegateEmail = [otherDelegate delegatedTo]; + if ([delegateEmail length]) + delegateEmail = [delegateEmail rfc822Email]; + + if ([delegateEmail length]) + otherDelegate = [event findAttendeeWithEmail: delegateEmail]; + else + otherDelegate = NO; + } + } if (addDelegate) [event addToAttendees: delegate]; - - [otherAttendee setPartStat: [attendee partStat]]; + + [otherAttendee setPartStat: [attendee partStat]]; [otherAttendee setDelegatedTo: [attendee delegatedTo]]; [otherAttendee setDelegatedFrom: [attendee delegatedFrom]]; - + // Remove the RSVP attribute, as an action from the attendee // was actually performed, and this confuses iCal (bug #1850) [[otherAttendee attributes] removeObjectForKey: @"RSVP"]; - - // If one has accepted / declined an invitation on behalf of - // the attendee, we add the user to the SENT-BY attribute. - if (b && ![[currentUser login] isEqualToString: [theOwnerUser login]]) - { - NSString *currentEmail, *quotedEmail; - currentEmail = [[currentUser allEmails] objectAtIndex: 0]; - quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", - currentEmail]; - [otherAttendee setValue: 0 ofAttribute: @"SENT-BY" + + // If one has accepted / declined an invitation on behalf of + // the attendee, we add the user to the SENT-BY attribute. + if (b && ![[currentUser login] isEqualToString: [theOwnerUser login]]) + { + NSString *currentEmail, *quotedEmail; + currentEmail = [[currentUser allEmails] objectAtIndex: 0]; + quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail]; + [otherAttendee setValue: 0 ofAttribute: @"SENT-BY" to: quotedEmail]; - } - else - { - // We must REMOVE any SENT-BY here. This is important since if A accepted - // the event for B and then, B changes by himself his participation status, - // we don't want to keep the previous SENT-BY attribute there. - [(NSMutableDictionary *)[otherAttendee attributes] removeObjectForKey: @"SENT-BY"]; - } - } + } + else + { + // We must REMOVE any SENT-BY here. This is important since if A accepted + // the event for B and then, B changes by himself his participation status, + // we don't want to keep the previous SENT-BY attribute there. + [(NSMutableDictionary *)[otherAttendee attributes] removeObjectForKey: @"SENT-BY"]; + } + } // We generate the updated iCalendar file and we save it // in the database. iCalString = [[event parent] versitString]; error = [eventObject saveContentString: iCalString]; } - + return error; } @@ -1035,8 +1101,8 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent - (NSException *) _handleAttendee: (iCalPerson *) attendee withDelegate: (iCalPerson *) delegate ownerUser: (SOGoUser *) theOwnerUser - statusChange: (NSString *) newStatus - inEvent: (iCalEvent *) event + statusChange: (NSString *) newStatus + inEvent: (iCalEvent *) event { NSString *currentStatus, *organizerUID; SOGoUser *ownerUser, *currentUser; @@ -1067,29 +1133,28 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent if (delegate) { if (otherDelegate) - { - // There was already a delegate - if (![delegate hasSameEmailAddress: otherDelegate]) - { - // The delegate has changed - removeDelegate = YES; - addDelegate = YES; - } - } + { + // There was already a delegate + if (![delegate hasSameEmailAddress: otherDelegate]) + { + // The delegate has changed + removeDelegate = YES; + addDelegate = YES; + } + } else - // There was no previous delegate - addDelegate = YES; + // There was no previous delegate + addDelegate = YES; } else { if (otherDelegate) - // The user has removed the delegate - removeDelegate = YES; + // The user has removed the delegate + removeDelegate = YES; } if (addDelegate || removeDelegate - || [currentStatus caseInsensitiveCompare: newStatus] - != NSOrderedSame) + || [currentStatus caseInsensitiveCompare: newStatus] != NSOrderedSame) { NSMutableArray *delegates; NSString *delegatedUID; @@ -1101,106 +1166,105 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // the attendee, we add the user to the SENT-BY attribute. currentUser = [context activeUser]; if (![[currentUser login] isEqualToString: [theOwnerUser login]]) - { + { NSString *currentEmail, *quotedEmail; currentEmail = [[currentUser allEmails] objectAtIndex: 0]; - quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", - currentEmail]; + quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail]; [attendee setValue: 0 ofAttribute: @"SENT-BY" to: quotedEmail]; - } + } else - { - // We must REMOVE any SENT-BY here. This is important since if A accepted - // the event for B and then, B changes by himself his participation status, - // we don't want to keep the previous SENT-BY attribute there. - [(NSMutableDictionary *)[attendee attributes] removeObjectForKey: @"SENT-BY"]; - } - + { + // We must REMOVE any SENT-BY here. This is important since if A accepted + // the event for B and then, B changes by himself his participation status, + // we don't want to keep the previous SENT-BY attribute there. + [(NSMutableDictionary *)[attendee attributes] removeObjectForKey: @"SENT-BY"]; + } + [attendee setDelegatedTo: [delegate email]]; - + if (removeDelegate) - { - delegates = [NSMutableArray array]; - - while (otherDelegate) - { - [delegates addObject: otherDelegate]; - - delegatedUID = [otherDelegate uid]; - if (delegatedUID) - // Delegate attendee is a local user; remove event from his calendar - [self _removeEventFromUID: delegatedUID - owner: [theOwnerUser login] - withRecurrenceId: [event recurrenceId]]; - - [event removeFromAttendees: otherDelegate]; - - // Verify if the delegate was already delegated - delegateEmail = [otherDelegate delegatedTo]; - if ([delegateEmail length]) - delegateEmail = [delegateEmail rfc822Email]; - - if ([delegateEmail length]) - otherDelegate = [event findAttendeeWithEmail: delegateEmail]; - else - otherDelegate = NO; - } - - [self sendEMailUsingTemplateNamed: @"Deletion" - forObject: [event itipEntryWithMethod: @"cancel"] - previousObject: nil - toAttendees: delegates + { + delegates = [NSMutableArray array]; + + while (otherDelegate) + { + [delegates addObject: otherDelegate]; + + delegatedUID = [otherDelegate uid]; + if (delegatedUID) + // Delegate attendee is a local user; remove event from his calendar + [self _removeEventFromUID: delegatedUID + owner: [theOwnerUser login] + withRecurrenceId: [event recurrenceId]]; + + [event removeFromAttendees: otherDelegate]; + + // Verify if the delegate was already delegated + delegateEmail = [otherDelegate delegatedTo]; + if ([delegateEmail length]) + delegateEmail = [delegateEmail rfc822Email]; + + if ([delegateEmail length]) + otherDelegate = [event findAttendeeWithEmail: delegateEmail]; + else + otherDelegate = NO; + } + + [self sendEMailUsingTemplateNamed: @"Deletion" + forObject: [event itipEntryWithMethod: @"cancel"] + previousObject: nil + toAttendees: delegates withType: @"calendar:cancellation"]; - } // if (removeDelegate) - + } // if (removeDelegate) + if (addDelegate) - { - delegatedUID = [delegate uid]; - delegates = [NSArray arrayWithObject: delegate]; - [event addToAttendees: delegate]; - - if (delegatedUID) - // Delegate attendee is a local user; add event to his calendar - [self _addOrUpdateEvent: event - forUID: delegatedUID - owner: [theOwnerUser login]]; - - [self sendEMailUsingTemplateNamed: @"Invitation" - forObject: [event itipEntryWithMethod: @"request"] - previousObject: nil - toAttendees: delegates + { + delegatedUID = [delegate uid]; + delegates = [NSArray arrayWithObject: delegate]; + [event addToAttendees: delegate]; + + if (delegatedUID) + // Delegate attendee is a local user; add event to his calendar + [self _addOrUpdateEvent: event + forUID: delegatedUID + owner: [theOwnerUser login]]; + + [self sendEMailUsingTemplateNamed: @"Invitation" + forObject: [event itipEntryWithMethod: @"request"] + previousObject: nil + toAttendees: delegates withType: @"calendar:invitation"]; - } // if (addDelegate) + } // if (addDelegate) // If the current user isn't the organizer of the event // that has just been updated, we update the event and // send a notification ownerUser = [SOGoUser userWithLogin: owner]; if (!(ex || [event userIsOrganizer: ownerUser])) - { - if ([event isStillRelevant]) - [self sendResponseToOrganizer: event + { + if ([event isStillRelevant]) + [self sendResponseToOrganizer: event from: ownerUser]; - - organizerUID = [[event organizer] uid]; - - // Event is an exception to a recurring event; retrieve organizer from master event - if (!organizerUID) - organizerUID = [[(iCalEntityObject*)[[event parent] firstChildWithTag: [self componentTag]] organizer] uid]; - - if (organizerUID) - // Update the attendee in organizer's calendar. - ex = [self _updateAttendee: attendee + + organizerUID = [[event organizer] uid]; + + // Event is an exception to a recurring event; retrieve organizer from master event + if (!organizerUID) + organizerUID = [[(iCalEntityObject*)[[event parent] firstChildWithTag: [self componentTag]] organizer] uid]; + + if (organizerUID) + // Update the attendee in organizer's calendar. + ex = [self _updateAttendee: attendee withDelegate: delegate ownerUser: theOwnerUser forEventUID: [event uid] withRecurrenceId: [event recurrenceId] withSequence: [event sequence] forUID: organizerUID - shouldAddSentBy: YES]; - } - + shouldAddSentBy: YES]; + } + // We update the calendar of all attendees that are // local to the system. This is useful in case user A accepts // invitation from organizer B and users C, D, E who are also @@ -1209,15 +1273,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent iCalPerson *att; NSString *uid; int i; - + attendees = [event attendees]; for (i = 0; i < [attendees count]; i++) - { - att = [attendees objectAtIndex: i]; - uid = [att uid]; - if (uid - && att != attendee - && ![uid isEqualToString: delegatedUID]) + { + att = [attendees objectAtIndex: i]; + uid = [att uid]; + if (uid && att != attendee && ![uid isEqualToString: delegatedUID]) [self _updateAttendee: attendee withDelegate: delegate ownerUser: theOwnerUser @@ -1226,9 +1288,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent withSequence: [event sequence] forUID: uid shouldAddSentBy: YES]; - } + } } - + return ex; } @@ -1238,13 +1300,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent NSDictionary *code; element = [NSMutableArray array]; - [element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, - recipient)]; - [element addObject: davElementWithContent (@"request-status", - XMLNS_CALDAV, - @"2.0;Success")]; - code = davElementWithContent (@"response", XMLNS_CALDAV, - element); + [element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, recipient)]; + [element addObject: davElementWithContent (@"request-status", XMLNS_CALDAV, @"2.0;Success")]; + code = davElementWithContent (@"response", XMLNS_CALDAV, element); return code; } @@ -1254,7 +1312,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // advertise for its support but we do everything within the calendar-auto-scheduling code // - (NSArray *) postCalDAVEventRequestTo: (NSArray *) recipients - from: (NSString *) originator + from: (NSString *) originator { NSEnumerator *recipientsEnum; NSMutableArray *elements; @@ -1267,14 +1325,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent while ((recipient = [recipientsEnum nextObject])) if ([[recipient lowercaseString] hasPrefix: @"mailto:"]) { - [elements addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; + [elements addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; } return elements; } - (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients - from: (NSString *) originator + from: (NSString *) originator { NSEnumerator *recipientsEnum; NSMutableArray *elements; @@ -1288,14 +1346,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent while ((recipient = [recipientsEnum nextObject])) if ([[recipient lowercaseString] hasPrefix: @"mailto:"]) { - [elements addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; + [elements addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; } return elements; } - (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients - from: (NSString *) originator + from: (NSString *) originator { NSEnumerator *recipientsEnum; NSMutableArray *elements; @@ -1307,7 +1365,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent while ((recipient = [recipientsEnum nextObject])) if ([[recipient lowercaseString] hasPrefix: @"mailto:"]) { - [elements addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; + [elements addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; } return elements; @@ -1320,7 +1378,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent withDelegate: (iCalPerson *) delegate { return [self changeParticipationStatus: status - withDelegate: delegate + withDelegate: delegate forRecurrenceId: nil]; } @@ -1371,15 +1429,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent attendee = [event userAsAttendee: ownerUser]; if (attendee) { - if (delegate - && ![[delegate email] isEqualToString: [attendee delegatedTo]]) + if (delegate && ![[delegate email] isEqualToString: [attendee delegatedTo]]) { delegatedUid = [delegate uid]; if (delegatedUid) delegatedUser = [SOGoUser userWithLogin: delegatedUid]; if (delegatedUser != nil && [event userIsOrganizer: delegatedUser]) ex = [NSException exceptionWithHTTPStatus: 403 - reason: @"delegate is organizer"]; + reason: @"delegate is organizer"]; if ([event isAttendee: [[delegate email] rfc822Email]]) ex = [NSException exceptionWithHTTPStatus: 403 reason: @"delegate is a participant"]; @@ -1405,9 +1462,8 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // the database. We do this ONLY when using SOGo from the // Web interface or over ActiveSync. // Over DAV, it'll be handled directly in PUTAction: - if (![context request] - || [[context request] handledByDefaultHandler] - || [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]) + if (![context request] || [[context request] handledByDefaultHandler] + || [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]) ex = [self saveContentString: [[event parent] versitString]]; } } @@ -1415,9 +1471,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent ex = [NSException exceptionWithHTTPStatus: 404 // Not Found reason: @"user does not participate in this calendar event"]; } - else - ex = [NSException exceptionWithHTTPStatus: 500 // Server Error - reason: @"unable to parse event record"]; + else + ex = [NSException exceptionWithHTTPStatus: 500 // Server Error + reason: @"unable to parse event record"]; return ex; } @@ -1438,20 +1494,20 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent if (theOrganizer && (v = [theOrganizer value: 0 ofAttribute: @"SCHEDULE-AGENT"])) { if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame || - [v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame) - b = NO; + [v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame) + b = NO; } - - // - // If we have to deal with Thunderbird/Lightning, we always send invitation - // reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT - // to NONE/CLIENT when responding to an external invitation received by - // SOGo - so no invitation responses are ever sent by Lightning. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and - // https://bugzilla.mozilla.org/show_bug.cgi?id=997784 - // - userAgents = [[context request] headersForKey: @"User-Agent"]; - + + // + // If we have to deal with Thunderbird/Lightning, we always send invitation + // reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT + // to NONE/CLIENT when responding to an external invitation received by + // SOGo - so no invitation responses are ever sent by Lightning. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=997784 + // + userAgents = [[context request] headersForKey: @"User-Agent"]; + for (i = 0; i < [userAgents count]; i++) { if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound && @@ -1504,36 +1560,36 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent #warning Make sure this is correct .. if (![attendees count] && event != occurence) - attendees = [event attendeesWithoutUser: currentUser]; + attendees = [event attendeesWithoutUser: currentUser]; if ([attendees count]) - { - // Remove the event from all attendees calendars - // and send them an email. - [self _handleRemovedUsers: attendees - withRecurrenceId: recurrenceId]; - [self sendEMailUsingTemplateNamed: @"Deletion" + { + // Remove the event from all attendees calendars + // and send them an email. + [self _handleRemovedUsers: attendees + withRecurrenceId: recurrenceId]; + [self sendEMailUsingTemplateNamed: @"Deletion" forObject: [occurence itipEntryWithMethod: @"cancel"] previousObject: nil toAttendees: attendees withType: @"calendar:cancellation"]; - } + } } else if ([occurence userIsAttendee: ownerUser]) { // The current user deletes the occurence; let the organizer know that // the user has declined this occurence. [self changeParticipationStatus: @"DECLINED" withDelegate: nil - forRecurrenceId: recurrenceId]; + forRecurrenceId: recurrenceId]; send_receipt = NO; } - - if (send_receipt) - [self sendReceiptEmailForObject: event - addedAttendees: nil - deletedAttendees: nil - updatedAttendees: nil - operation: EventDeleted]; + + if (send_receipt) + [self sendReceiptEmailForObject: event + addedAttendees: nil + deletedAttendees: nil + updatedAttendees: nil + operation: EventDeleted]; } - (NSException *) prepareDelete @@ -1616,9 +1672,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { event = [allEvents objectAtIndex: i]; if ([event isAllDay] && [event isOpaque]) - { - [event setTransparency: @"TRANSPARENT"]; - } + [event setTransparency: @"TRANSPARENT"]; } } @@ -1690,10 +1744,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent event = [allEvents objectAtIndex: i]; if ([event recurrenceId] && !theRecurrenceId) - return event; - + return event; + if ([event recurrenceId] && [[event recurrenceId] compare: theRecurrenceId] == NSOrderedSame) - return event; + return event; } return nil; @@ -1744,12 +1798,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent NSArray *roles; SOGoUser *ownerUser; - if (calendar == fullCalendar - || calendar == safeCalendar - || calendar == originalCalendar) - [NSException raise: NSInvalidArgumentException - format: @"the 'calendar' argument must be a distinct instance" - @" from the original object"]; + if (calendar == fullCalendar || calendar == safeCalendar + || calendar == originalCalendar) + [NSException raise: NSInvalidArgumentException format: @"the 'calendar' argument must be a distinct instance" @" from the original object"]; ownerUser = [SOGoUser userWithLogin: owner]; @@ -1760,24 +1811,21 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // responding to one of our invitation. In this case, _setupResponseCalendarInRequest // will only take the new attendee status and actually discard any other modifications. // - if ([roles containsObject: @"ComponentResponder"] - && ![roles containsObject: @"ComponentModifier"]) + if ([roles containsObject: @"ComponentResponder"] && ![roles containsObject: @"ComponentModifier"]) calendar = [self _setupResponseInRequestCalendar: calendar]; else { - if (![[rq headersForKey: @"X-SOGo"] - containsObject: @"NoGroupsDecomposition"]) + if (![[rq headersForKey: @"X-SOGo"] containsObject: @"NoGroupsDecomposition"]) [self _decomposeGroupsInRequestCalendar: calendar]; - - if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency] - && [rq isIPhone]) - { - [self _adjustTransparencyInRequestCalendar: calendar]; - } - + + if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency] && [rq isIPhone]) + { + [self _adjustTransparencyInRequestCalendar: calendar]; + } + [self _adjustEventsInRequestCalendar: calendar]; } - + // // We first check if it's a new event // @@ -1798,47 +1846,46 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1) if ([container resourceNameForEventUID: eventUID]) { - return [NSException exceptionWithHTTPStatus: 403 - reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]]; + return [NSException exceptionWithHTTPStatus: 403 + reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]]; } // // New event and we're the organizer -- send invitation to all attendees // if (scheduling && [event userIsOrganizer: ownerUser]) - { - attendees = [event attendeesWithoutUser: ownerUser]; - if ([attendees count]) - { - if ((ex = [self _handleAddedUsers: attendees fromEvent: event])) - return ex; - else - { - // We might have auto-accepted resources here. If that's the - // case, let's regenerate the versitstring and replace the - // one from the request. - [rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]]; - } - - [self sendEMailUsingTemplateNamed: @"Invitation" - forObject: [event itipEntryWithMethod: @"request"] - previousObject: nil - toAttendees: attendees - withType: @"calendar:invitation"]; - } - } + { + attendees = [event attendeesWithoutUser: ownerUser]; + if ([attendees count]) + { + if ((ex = [self _handleAddedUsers: attendees fromEvent: event])) + return ex; + else + { + // We might have auto-accepted resources here. If that's the + // case, let's regenerate the versitstring and replace the + // one from the request. + [rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]]; + } + + [self sendEMailUsingTemplateNamed: @"Invitation" + forObject: [event itipEntryWithMethod: @"request"] + previousObject: nil + toAttendees: attendees + withType: @"calendar:invitation"]; + } + } // // We aren't the organizer but we're an attendee. That can happen when // we receive an external invitation (IMIP/ITIP) and we accept it // from a CUA - it gets added to a specific CalDAV calendar using a PUT // else if (scheduling && [event userIsAttendee: ownerUser]) - { - [self sendIMIPReplyForEvent: event - from: ownerUser - to: [event organizer]]; - } - + { + [self sendIMIPReplyForEvent: event + from: ownerUser + to: [event organizer]]; + } [self sendReceiptEmailForObject: event addedAttendees: attendees @@ -1865,198 +1912,197 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent recurrenceId = nil; for (i = [newEvents count]-1; i >= 0; i--) - { - newEvent = [newEvents objectAtIndex: i]; - - if ([newEvent recurrenceId]) - { - // Find the corresponding RECURRENCE-ID in the old calendar - // If not present, we assume it was created before the PUT - oldEvent = [self _eventFromRecurrenceId: [newEvent recurrenceId] - events: oldEvents]; - - if (oldEvent == nil) - { - NSString *recurrenceTime; - recurrenceTime = [NSString stringWithFormat: @"%f", [[newEvent recurrenceId] timeIntervalSince1970]]; - oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime]; - } - - // If present, we look for changes - changes = [iCalEventChanges changesFromEvent: oldEvent toEvent: newEvent]; - - if ([changes sequenceShouldBeIncreased] | [changes hasAttendeeChanges]) - { - // We found a RECURRENCE-ID with changes, we consider it - recurrenceId = [newEvent recurrenceId]; - break; - } - else - { + { + newEvent = [newEvents objectAtIndex: i]; + + if ([newEvent recurrenceId]) + { + // Find the corresponding RECURRENCE-ID in the old calendar + // If not present, we assume it was created before the PUT + oldEvent = [self _eventFromRecurrenceId: [newEvent recurrenceId] + events: oldEvents]; + + if (oldEvent == nil) + { + NSString *recurrenceTime; + recurrenceTime = [NSString stringWithFormat: @"%f", [[newEvent recurrenceId] timeIntervalSince1970]]; + oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime]; + } + + // If present, we look for changes + changes = [iCalEventChanges changesFromEvent: oldEvent toEvent: newEvent]; + + if ([changes sequenceShouldBeIncreased] | [changes hasAttendeeChanges]) + { + // We found a RECURRENCE-ID with changes, we consider it + recurrenceId = [newEvent recurrenceId]; + break; + } + else + { [newEvents removeObject: newEvent]; [oldEvents removeObject: oldEvent]; - } - } - - oldEvent = nil; - newEvent = nil; - } + } + } + + oldEvent = nil; + newEvent = nil; + } // If no changes were observed, let's see if we have any left overs // in the oldEvents or in the newEvents array if (!oldEvent && !newEvent) - { - // We check if we only have to deal with the MASTER event - if ([newEvents count] == [oldEvents count]) - { - oldEvent = [oldEvents objectAtIndex: 0]; - newEvent = [newEvents objectAtIndex: 0]; - } - // A RECURRENCE-ID was added - else if ([newEvents count] > [oldEvents count]) - { - oldEvent = nil; - newEvent = [self _eventFromRecurrenceId: nil events: newEvents]; - recurrenceId = [newEvent recurrenceId]; - } - // A RECURRENCE-ID was removed - else - { - oldEvent = [self _eventFromRecurrenceId: nil events: oldEvents]; - newEvent = nil; - recurrenceId = [oldEvent recurrenceId]; - } - } - + { + // We check if we only have to deal with the MASTER event + if ([newEvents count] == [oldEvents count]) + { + oldEvent = [oldEvents objectAtIndex: 0]; + newEvent = [newEvents objectAtIndex: 0]; + } + // A RECURRENCE-ID was added + else if ([newEvents count] > [oldEvents count]) + { + oldEvent = nil; + newEvent = [self _eventFromRecurrenceId: nil events: newEvents]; + recurrenceId = [newEvent recurrenceId]; + } + // A RECURRENCE-ID was removed + else + { + oldEvent = [self _eventFromRecurrenceId: nil events: oldEvents]; + newEvent = nil; + recurrenceId = [oldEvent recurrenceId]; + } + } + // We check if the PUT call is actually an PART-STATE change // from one of the attendees - here's the logic : // - // if owner == organizer + // if owner == organizer // // if [context activeUser] == organizer // [send the invitation update] // else // [react on SENT-BY as someone else is acting for the organizer] - // - // + // + // if ([[newEvent attendees] count] || [[oldEvent attendees] count]) - { - NSString *uid; + { + NSString *uid; // newEvent might be nil here, if we're deleting a RECURRENCE-ID with attendees // If that's the case, we use the oldEvent for now just to obtain the organizer // and we'll swap it back to nil once we're done. if (!newEvent) newEvent = oldEvent; - - // We fetch the organizer's uid. Sometimes, the recurrence-id will - // have it, sometimes not. If it doesn't, we fetch it from the master event. - uid = [[newEvent organizer] uid]; - - if (!uid && !recurrenceId) - uid = [[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid]; - - // With Thunderbird 10, if you create a recurring event with an exception - // occurence, and invite someone, the PUT will have the organizer in the - // recurrence-id and not in the master event. We must fix this, otherwise - // SOGo will break. - if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid]) - [[[[newEvent parent] events] objectAtIndex: 0] - setOrganizer: [newEvent organizer]]; - + + // We fetch the organizer's uid. Sometimes, the recurrence-id will + // have it, sometimes not. If it doesn't, we fetch it from the master event. + uid = [[newEvent organizer] uid]; + + if (!uid && !recurrenceId) + uid = [[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid]; + + // With Thunderbird 10, if you create a recurring event with an exception + // occurence, and invite someone, the PUT will have the organizer in the + // recurrence-id and not in the master event. We must fix this, otherwise + // SOGo will break. + if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid]) + [[[[newEvent parent] events] objectAtIndex: 0] setOrganizer: [newEvent organizer]]; + if (newEvent == oldEvent) newEvent = nil; - - if (uid && [uid caseInsensitiveCompare: owner] == NSOrderedSame) - { + + if (uid && [uid caseInsensitiveCompare: owner] == NSOrderedSame) + { // A RECCURENCE-ID was removed if (!newEvent && oldEvent) [self prepareDeleteOccurence: oldEvent]; // The master event was changed, A RECCURENCE-ID was added or modified else if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) - return ex; - } - // - // else => attendee is responding - // - // if [context activeUser] == attendee - // [we change the PART-STATE] - // else - // [react on SENT-BY as someone else is acting for the attendee] - else - { - iCalPerson *attendee, *delegate; - NSString *delegateEmail; - - attendee = [newEvent userAsAttendee: [SOGoUser userWithLogin: owner]]; - - // We first check of the sequences are alright. We don't accept attendees - // accepting "old" invitations. If that's the case, we return a 403 + return ex; + } + // + // else => attendee is responding + // + // if [context activeUser] == attendee + // [we change the PART-STATE] + // else + // [react on SENT-BY as someone else is acting for the attendee] + else + { + iCalPerson *attendee, *delegate; + NSString *delegateEmail; + + attendee = [newEvent userAsAttendee: [SOGoUser userWithLogin: owner]]; + + // We first check of the sequences are alright. We don't accept attendees + // accepting "old" invitations. If that's the case, we return a 403 if ([[newEvent sequence] intValue] < [[oldEvent sequence] intValue]) - return [NSException exceptionWithHTTPStatus:403 - reason: @"sequences don't match"]; - + return [NSException exceptionWithHTTPStatus:403 + reason: @"sequences don't match"]; + // Remove the RSVP attribute, as an action from the attendee // was actually performed, and this confuses iCal (bug #1850) - [[attendee attributes] removeObjectForKey: @"RSVP"]; - - delegate = nil; - delegateEmail = [attendee delegatedTo]; - - if ([delegateEmail length]) - { - delegateEmail = [delegateEmail substringFromIndex: 7]; - if ([delegateEmail length]) - delegate = [newEvent findAttendeeWithEmail: delegateEmail]; - } - - changes = [iCalEventChanges changesFromEvent: oldEvent toEvent: newEvent]; - - // The current user deletes the occurence; let the organizer know that - // the user has declined this occurence. - if ([[changes updatedProperties] containsObject: @"exdate"]) - { - [self changeParticipationStatus: @"DECLINED" - withDelegate: nil // FIXME (specify delegate?) - forRecurrenceId: [self _addedExDate: oldEvent newEvent: newEvent]]; - } - else if (attendee) - { - [self changeParticipationStatus: [attendee partStat] - withDelegate: delegate - forRecurrenceId: recurrenceId]; - } - // All attendees and the organizer field were removed. Apple iCal does - // that when we remove the last attendee of an event. - // - // We must update previous's attendees' calendars to actually - // remove the event in each of them. - else - { - [self _handleRemovedUsers: [changes deletedAttendees] - withRecurrenceId: recurrenceId]; - } - } - } // if ([[newEvent attendees] count] || [[oldEvent attendees] count]) + [[attendee attributes] removeObjectForKey: @"RSVP"]; + + delegate = nil; + delegateEmail = [attendee delegatedTo]; + + if ([delegateEmail length]) + { + delegateEmail = [delegateEmail substringFromIndex: 7]; + if ([delegateEmail length]) + delegate = [newEvent findAttendeeWithEmail: delegateEmail]; + } + + changes = [iCalEventChanges changesFromEvent: oldEvent toEvent: newEvent]; + + // The current user deletes the occurence; let the organizer know that + // the user has declined this occurence. + if ([[changes updatedProperties] containsObject: @"exdate"]) + { + [self changeParticipationStatus: @"DECLINED" + withDelegate: nil // FIXME (specify delegate?) + forRecurrenceId: [self _addedExDate: oldEvent newEvent: newEvent]]; + } + else if (attendee) + { + [self changeParticipationStatus: [attendee partStat] + withDelegate: delegate + forRecurrenceId: recurrenceId]; + } + // All attendees and the organizer field were removed. Apple iCal does + // that when we remove the last attendee of an event. + // + // We must update previous's attendees' calendars to actually + // remove the event in each of them. + else + { + [self _handleRemovedUsers: [changes deletedAttendees] + withRecurrenceId: recurrenceId]; + } + } + } // if ([[newEvent attendees] count] || [[oldEvent attendees] count]) else - { - [self sendReceiptEmailForObject: newEvent - addedAttendees: nil - deletedAttendees: nil - updatedAttendees: nil - operation: EventUpdated]; - } + { + [self sendReceiptEmailForObject: newEvent + addedAttendees: nil + deletedAttendees: nil + updatedAttendees: nil + operation: EventUpdated]; + } } // else of if (isNew) ... - + unsigned int baseVersion; // We must NOT invoke [super PUTAction:] here as it'll resave // the content string and we could have etag mismatches. baseVersion = (isNew ? 0 : version); - + ex = [self saveContentString: [calendar versitString] - baseVersion: baseVersion]; - - return ex; + baseVersion: baseVersion]; + + return ex; } // @@ -2086,24 +2132,24 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // ex = [self matchesRequestConditionInContext: context]; if (ex) - return ex; + return ex; } - - ex = [self updateContentWithCalendar: rqCalendar fromRequest: rq]; - if (ex) - response = (WOResponse *) ex; + + ex = [self updateContentWithCalendar: rqCalendar fromRequest: rq]; + if (ex) + response = (WOResponse *) ex; else { response = [_ctx response]; if (isNew) - [response setStatus: 201 /* Created */]; + [response setStatus: 201 /* Created */]; else - [response setStatus: 204 /* No Content */]; + [response setStatus: 204 /* No Content */]; etag = [self davEntityTag]; if (etag) - [response setHeader: etag forKey: @"etag"]; + [response setHeader: etag forKey: @"etag"]; } - + return response; } diff --git a/SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings b/SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings index c855a96bf..5add824eb 100644 --- a/SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings +++ b/SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings @@ -1,3 +1,4 @@ +"Inviting the following persons is prohibited:" = "Esta prohibido invitar las siguientes personas:"; "Personal Calendar" = "Calendario personal"; vevent_class0 = "(Evento público)"; vevent_class1 = "(Evento privado)"; diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index 0405a8ebe..cf4e06d27 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -53,6 +53,7 @@ { NSString *path; int IMAP4ID; + int sourceIMAP4ID; NSMutableDictionary *headers; NSString *inReplyTo; NSString *text; @@ -87,6 +88,9 @@ - (void) setSourceFolder: (NSString *) newSourceFolder; - (NSString *) sourceFolder; +- (void) setSourceIMAP4ID: (int) newSourceIMAPID; +- (int) sourceIMAP4ID; + - (void) setIMAP4ID: (int) newIMAPID; - (int) IMAP4ID; diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 4d0f9aa38..764963534 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -197,6 +197,7 @@ static NSString *userAgent = nil; { if ((self = [super init])) { + sourceIMAP4ID = -1; IMAP4ID = -1; headers = [NSMutableDictionary new]; text = @""; @@ -234,8 +235,7 @@ static NSString *userAgent = nil; { if (!path) { - path = [[self userSpoolFolderPath] stringByAppendingPathComponent: - nameInContainer]; + path = [[self userSpoolFolderPath] stringByAppendingPathComponent: nameInContainer]; [path retain]; } @@ -466,6 +466,9 @@ static NSString *userAgent = nil; forKey: @"isHTML"]; if (inReplyTo) [infos setObject: inReplyTo forKey: @"inReplyTo"]; + if (sourceIMAP4ID > -1) + [infos setObject: [NSString stringWithFormat: @"%i", sourceIMAP4ID] + forKey: @"sourceIMAP4ID"]; if (IMAP4ID > -1) [infos setObject: [NSString stringWithFormat: @"%i", IMAP4ID] forKey: @"IMAP4ID"]; @@ -513,6 +516,10 @@ static NSString *userAgent = nil; [self setText: value]; isHTML = [[infoDict objectForKey: @"isHTML"] boolValue]; + value = [infoDict objectForKey: @"sourceIMAP4ID"]; + if (value) + [self setSourceIMAP4ID: [value intValue]]; + value = [infoDict objectForKey: @"IMAP4ID"]; if (value) [self setIMAP4ID: [value intValue]]; @@ -564,6 +571,22 @@ static NSString *userAgent = nil; [self debugWithFormat: @"Note: info object does not yet exist: %@", p]; } +// +// +// +- (void) setSourceIMAP4ID: (int) newSourceIMAP4ID +{ + sourceIMAP4ID = newSourceIMAP4ID; +} + +// +// +// +- (int) sourceIMAP4ID +{ + return sourceIMAP4ID; +} + // // // @@ -610,7 +633,7 @@ static NSString *userAgent = nil; { if (IMAP4ID > -1) error = [imap4 markURLDeleted: [self imap4URL]]; - IMAP4ID = [self IMAP4IDFromAppendResult: result]; + [self setIMAP4ID: [self IMAP4IDFromAppendResult: result]]; if (imap4URL) { // Invalidate the IMAP message URL since the message ID has changed @@ -902,9 +925,7 @@ static NSString *userAgent = nil; [self setHeaders: info]; [self setText: [sourceMail contentForEditing]]; - [self setSourceURL: [sourceMail imap4URLString]]; [self setIMAP4ID: [[sourceMail nameInContainer] intValue]]; - [self setSourceFolderWithMailObject: sourceMail]; [self storeInfo]; } @@ -934,7 +955,7 @@ static NSString *userAgent = nil; [self setHeaders: info]; [self setSourceURL: [sourceMail imap4URLString]]; [self setSourceFlag: @"Answered"]; - [self setIMAP4ID: [[sourceMail nameInContainer] intValue]]; + [self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]]; [self setSourceFolderWithMailObject: sourceMail]; [self storeInfo]; @@ -957,7 +978,7 @@ static NSString *userAgent = nil; [self setSourceURL: [sourceMail imap4URLString]]; [self setSourceFlag: @"$Forwarded"]; - [self setIMAP4ID: [[sourceMail nameInContainer] intValue]]; + [self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]]; [self setSourceFolderWithMailObject: sourceMail]; /* attach message */ diff --git a/SoObjects/SOGo/SOGoCacheGCSObject.m b/SoObjects/SOGo/SOGoCacheGCSObject.m index 03269d9d7..0b3ba25e8 100644 --- a/SoObjects/SOGo/SOGoCacheGCSObject.m +++ b/SoObjects/SOGo/SOGoCacheGCSObject.m @@ -456,6 +456,40 @@ static EOAttribute *textColumn = nil; return nil; } +- (NSException *) destroy +{ + NSString *tableName, *pathValue, *sql; + EOAdaptorChannel *channel; + GCSChannelManager *cm; + NSException *result; + EOAdaptor *adaptor; + + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: [self tableUrl]]; + tableName = [self tableName]; + + adaptor = [[channel adaptorContext] adaptor]; + pathValue = [adaptor formatValue: [self path] + forAttribute: textColumn]; + result = nil; + + sql = [NSString stringWithFormat: + (@"DELETE FROM %@" + @" WHERE c_path = %@"), + tableName, + pathValue]; + + result = [channel evaluateExpressionX: sql]; + + if (result) + [self errorWithFormat: @"could not delete record %@" + @" in %@: %@", pathValue, tableName, result]; + + [cm releaseChannel: channel]; + + return result; +} + - (void) save { NSString *sql; diff --git a/SoObjects/SOGo/SOGoCacheObject.h b/SoObjects/SOGo/SOGoCacheObject.h index 0d0a78492..d8b01c468 100644 --- a/SoObjects/SOGo/SOGoCacheObject.h +++ b/SoObjects/SOGo/SOGoCacheObject.h @@ -42,6 +42,8 @@ - (NSCalendarDate *) creationDate; - (NSCalendarDate *) lastModified; +- (NSException *) destroy; + @end #endif /* SOGOCACHEOBJECT_H */ diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index b0110f58d..d23e05013 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -216,18 +216,17 @@ static NSArray *childRecordFields = nil; - (void) setFolderPropertyValue: (id) theValue inCategory: (NSString *) theKey + settings: (SOGoUserSettings *) theSettings { - SOGoUserSettings *settings; NSMutableDictionary *folderSettings, *values; NSString *module; - settings = [[context activeUser] userSettings]; module = [container nameInContainer]; - folderSettings = [settings objectForKey: module]; + folderSettings = [theSettings objectForKey: module]; if (!folderSettings) { folderSettings = [NSMutableDictionary dictionary]; - [settings setObject: folderSettings forKey: module]; + [theSettings setObject: folderSettings forKey: module]; } values = [folderSettings objectForKey: theKey]; if (theValue) @@ -249,7 +248,19 @@ static NSArray *childRecordFields = nil; [folderSettings removeObjectForKey: theKey]; } - [settings synchronize]; + [theSettings synchronize]; +} + +- (void) setFolderPropertyValue: (id) theValue + inCategory: (NSString *) theKey +{ + SOGoUserSettings *settings; + + settings = [[context activeUser] userSettings]; + + [self setFolderPropertyValue: theValue + inCategory: theKey + settings: settings]; } - (id) folderPropertyValueInCategory: (NSString *) theKey @@ -281,34 +292,19 @@ static NSArray *childRecordFields = nil; SOGoDomainDefaults *dd; primaryDN = [row objectForKey: @"c_foldername"]; + if ([primaryDN length]) - { - displayName = [NSMutableString new]; - if ([primaryDN isEqualToString: [container defaultFolderName]]) - [displayName appendString: [self labelForKey: primaryDN - inContext: context]]; - else - [displayName appendString: primaryDN]; - - if (!activeUserIsOwner) { - // We MUST NOT use SOGoUser instances here (by calling -primaryIdentity) - // as it'll load user defaults and user settings which is _very costly_ - // since it involves JSON parsing and database requests - ownerIdentity = [[SOGoUserManager sharedUserManager] - contactInfosForUserWithUIDorEmail: owner]; - - folderSubscriptionValues = [[NSMutableDictionary alloc] initWithObjectsAndKeys: displayName, @"FolderName", - [ownerIdentity objectForKey: @"cn"], @"UserName", - [ownerIdentity objectForKey: @"c_email"], @"Email", nil]; + DESTROY(displayName); - dd = [[context activeUser] domainDefaults]; - subjectFormat = [dd subscriptionFolderFormat]; + if ([primaryDN isEqualToString: [container defaultFolderName]]) + displayName = [self labelForKey: primaryDN + inContext: context]; + else + displayName =primaryDN; - displayName = [folderSubscriptionValues keysWithFormat: subjectFormat]; - [displayName retain]; + RETAIN(displayName); } - } } /* This method fetches the display name defined by the owner, but is also the @@ -365,6 +361,7 @@ static NSArray *childRecordFields = nil; else { [self _fetchDisplayNameFromSubscriber]; + if (!displayName) [self _fetchDisplayNameFromOwner]; } @@ -856,11 +853,15 @@ static NSArray *childRecordFields = nil; - (BOOL) subscribeUserOrGroup: (NSString *) theIdentifier reallyDo: (BOOL) reallyDo + response: (WOResponse *) theResponse { + NSDictionary *ownerIdentity, *folderSubscriptionValues; NSMutableDictionary *moduleSettings, *folderShowAlarms; + NSString *subjectFormat, *formattedName; NSMutableArray *folderSubscription; NSString *subscriptionPointer; NSMutableArray *allUsers; + SOGoDomainDefaults *dd; SOGoUserSettings *us; NSDictionary *dict; SOGoUser *sogoUser; @@ -892,6 +893,26 @@ static NSArray *childRecordFields = nil; } rc = NO; + + // We MUST NOT use SOGoUser instances here (by calling -primaryIdentity) + // as it'll load user defaults and user settings which is _very costly_ + // since it involves JSON parsing and database requests + ownerIdentity = [[SOGoUserManager sharedUserManager] + contactInfosForUserWithUIDorEmail: owner]; + + + folderSubscriptionValues = [[NSDictionary alloc] initWithObjectsAndKeys: [self displayName], @"FolderName", + [ownerIdentity objectForKey: @"cn"], @"UserName", + [ownerIdentity objectForKey: @"c_email"], @"Email", nil]; + + dd = [[context activeUser] domainDefaults]; + subjectFormat = [dd subscriptionFolderFormat]; + + formattedName = [folderSubscriptionValues keysWithFormat: subjectFormat]; + + // This is consumed by SOGo Integrator during folder subscription since v24.0.6 + if (theResponse) + [theResponse appendContentString: formattedName]; for (i = 0; i < [allUsers count]; i++) { @@ -929,6 +950,10 @@ static NSArray *childRecordFields = nil; forKey: @"FolderShowAlarms"]; } + [self setFolderPropertyValue: formattedName + inCategory: @"FolderDisplayNames" + settings: us]; + [folderSubscription addObjectUniquely: subscriptionPointer]; // By default, we disable alarms on subscribed calendars @@ -987,7 +1012,7 @@ static NSArray *childRecordFields = nil; response = [context response]; [response setHeader: @"text/plain; charset=utf-8" forKey: @"Content-Type"]; - [response setStatus: 204]; + [response setStatus: 200]; currentUser = [context activeUser]; delegatedUsers = [self _parseDAVDelegatedUsers]; @@ -1002,7 +1027,8 @@ static NSArray *childRecordFields = nil; create contention on GDNC. */ for (count = 0; count < max; count++) [self subscribeUserOrGroup: [delegatedUsers objectAtIndex: count] - reallyDo: reallyDo]; + reallyDo: reallyDo + response: response]; } else { @@ -1021,7 +1047,9 @@ static NSArray *childRecordFields = nil; @"You cannot (un)subscribe to a folder that you own!"]; } else - [self subscribeUserOrGroup: userLogin reallyDo: reallyDo]; + [self subscribeUserOrGroup: userLogin + reallyDo: reallyDo + response: response]; } return response; diff --git a/SoObjects/SOGo/SOGoUserFolder.m b/SoObjects/SOGo/SOGoUserFolder.m index c450dc0cf..9292f9ffa 100644 --- a/SoObjects/SOGo/SOGoUserFolder.m +++ b/SoObjects/SOGo/SOGoUserFolder.m @@ -153,13 +153,15 @@ - (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder { - NSMutableArray *folders; - NSEnumerator *subfolders; - SOGoFolder *currentFolder; - NSString *folderName, *folderOwner; - Class subfolderClass; + NSDictionary *folderSubscriptionValues, *ownerIdentity; + NSString *folderName, *folderOwner, *formattedName; NSMutableDictionary *currentDictionary; SoSecurityManager *securityManager; + SOGoFolder *currentFolder; + NSEnumerator *subfolders; + NSMutableArray *folders; + SOGoDomainDefaults *dd; + Class subfolderClass; folders = [NSMutableArray array]; @@ -190,6 +192,18 @@ forKey: @"owner"]; [currentDictionary setObject: [currentFolder folderType] forKey: @"type"]; + + dd = [[context activeUser] domainDefaults]; + ownerIdentity = [[SOGoUserManager sharedUserManager] + contactInfosForUserWithUIDorEmail: owner]; + folderSubscriptionValues = [[NSDictionary alloc] initWithObjectsAndKeys: [currentFolder displayName], @"FolderName", + [ownerIdentity objectForKey: @"cn"], @"UserName", + [ownerIdentity objectForKey: @"c_email"], @"Email", nil]; + + formattedName = [folderSubscriptionValues keysWithFormat: [dd subscriptionFolderFormat]]; + [currentDictionary setObject: formattedName + forKey: @"formattedName"]; + [folders addObject: currentDictionary]; } } diff --git a/Tests/Integration/utilities.py b/Tests/Integration/utilities.py index 3b7f31441..850cb07a8 100644 --- a/Tests/Integration/utilities.py +++ b/Tests/Integration/utilities.py @@ -101,7 +101,7 @@ class TestACLUtility(TestUtility): post = webdavlib.HTTPPOST(self.resource, subscribeQuery) post.content_type = "application/xml; charset=\"utf-8\"" self.client.execute(post) - self.test.assertEquals(post.response["status"], 204, + self.test.assertEquals(post.response["status"], 200, "subscribtion failure to '%s' for '%s' (status: %d)" % (self.resource, "', '".join(subscribers), post.response["status"])) diff --git a/UI/Common/German.lproj/Localizable.strings b/UI/Common/German.lproj/Localizable.strings index a9f5e1458..85d47b658 100644 --- a/UI/Common/German.lproj/Localizable.strings +++ b/UI/Common/German.lproj/Localizable.strings @@ -13,7 +13,7 @@ "Administration" = "Administration"; "Disconnect" = "Beenden"; "Right Administration" = "Rechteadministration"; -"Log Console (dev.)" = "Journal (dev.)"; +"Log Console (dev.)" = "Journal (Entwickler)"; "User" = "Benutzer"; "Vacation message is enabled" = "Abwesenheitsmeldung ist eingeschaltet"; diff --git a/UI/Common/UIxAclEditor.m b/UI/Common/UIxAclEditor.m index a50e0ff07..8c39a7ebd 100644 --- a/UI/Common/UIxAclEditor.m +++ b/UI/Common/UIxAclEditor.m @@ -1,8 +1,6 @@ /* UIxAclEditor.m - this file is part of SOGo * - * Copyright (C) 2006-2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2014 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -138,7 +136,7 @@ - (BOOL) canSubscribeUsers { return [[self clientObject] - respondsToSelector: @selector (subscribeUserOrGroup:reallyDo:)]; + respondsToSelector: @selector (subscribeUserOrGroup:reallyDo:response:)]; } - (BOOL) currentUserIsSubscribed diff --git a/UI/Common/UIxFolderActions.m b/UI/Common/UIxFolderActions.m index fb0a95eeb..74f0134c7 100644 --- a/UI/Common/UIxFolderActions.m +++ b/UI/Common/UIxFolderActions.m @@ -89,7 +89,10 @@ } else { - [clientObject subscribeUserOrGroup: login reallyDo: reallyDo]; + [clientObject subscribeUserOrGroup: login + reallyDo: reallyDo + response: response]; + if (isMailInvitation) { mailInvitationURL @@ -373,7 +376,8 @@ max = [userIDs count]; for (count = 0; count < max; count++) [folder subscribeUserOrGroup: [userIDs objectAtIndex: count] - reallyDo: YES]; + reallyDo: YES + response: response]; ex = nil; } else diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 7160f6ba2..09d48ae5a 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -146,34 +146,34 @@ static NSData* _sanitizeContent(NSData *theData) const char *bytes; char *buf; int i, j, len; - BOOL found_delimiter; + BOOL found_delimiter, in_meta; d = [NSMutableData dataWithData: theData]; bytes = [d bytes]; len = [d length]; i = 0; + in_meta = NO; + while (i < len) { - // We check if we see in which case, we don't do any kind - // of substitution there after. - if (i < len-6) + // We check if we see in which case, we substitute de charset= stuff. + if (i < len-5) { if ((*bytes == '<') && - (*(bytes+1) == '/') && - (*(bytes+2) == 'h' || *(bytes+2) == 'H') && - (*(bytes+3) == 'e' || *(bytes+3) == 'E') && - (*(bytes+4) == 'a' || *(bytes+4) == 'A') && - (*(bytes+5) == 'd' || *(bytes+5) == 'D') && - (*(bytes+6) == '>')) - break; + (*(bytes+1) == 'm' || *(bytes+2) == 'M') && + (*(bytes+2) == 'e' || *(bytes+3) == 'E') && + (*(bytes+3) == 't' || *(bytes+4) == 'T') && + (*(bytes+4) == 'a' || *(bytes+5) == 'A') && + (*(bytes+5) == ' ')) + in_meta = YES; } // We search for something like : // // // - if (i < len-9) + if (in_meta && i < len-9) { if ((*bytes == 'c' || *bytes == 'C') && (*(bytes+1) == 'h' || *(bytes+1) == 'H') && @@ -195,16 +195,18 @@ static NSData* _sanitizeContent(NSData *theData) // We haven't found anything, let's return the data untouched if ((i+j) >= len) { - found_delimiter = NO; + in_meta = found_delimiter = NO; break; } } if (found_delimiter) - [d replaceBytesInRange: NSMakeRange(i, j) - withBytes: NULL - length: 0]; - break; + { + [d replaceBytesInRange: NSMakeRange(i, j) + withBytes: NULL + length: 0]; + in_meta = found_delimiter = NO; + } } } diff --git a/UI/MailPartViewers/product.plist b/UI/MailPartViewers/product.plist index dd749362d..225c9e7f0 100644 --- a/UI/MailPartViewers/product.plist +++ b/UI/MailPartViewers/product.plist @@ -1,5 +1,5 @@ { /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ - requires = ( MAIN ); + requires = ( MAIN, Mailer, Appointments ); publicResources = ( ); diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index f78843b53..a140d1a2e 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -808,7 +808,7 @@ static NSArray *infoKeys = nil; jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: @"success", @"status", [co sourceFolder], @"sourceFolder", - [NSNumber numberWithInt: [co IMAP4ID]], @"messageID", + [NSNumber numberWithInt: [co sourceIMAP4ID]], @"sourceMessageID", nil]; recipients_count += [[co allRecipients] count]; diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 95d7a72de..36f149b72 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -1,8 +1,6 @@ /* SOGoUserHomePage.m - this file is part of SOGo * - * Copyright (C) 2007-2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2014 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -444,10 +442,8 @@ [response setStatus: 200]; [response setHeader: @"text/plain; charset=utf-8" forKey: @"Content-Type"]; - foldersEnum = [folders objectEnumerator]; - while ((currentFolder = [foldersEnum nextObject])) - [response appendContentString: - [currentFolder keysWithFormat: @";%{displayName}:%{name}:%{type}"]]; + + [response appendContentString: [folders JSONRepresentation]]; return response; } diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index 66f8729e0..7c31da1f4 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -104,6 +104,10 @@ "firstWeekOfYear_First4DayWeek" = "First 4-day week"; "firstWeekOfYear_FirstFullWeek" = "First full week"; +"Prevent from being invited to appointments" = "Prevent from being invited to appointments"; +"White list for appointment invitations:" = "White list for appointment invitations:"; +"Contacts Names" = "Contacts Names"; + /* Default Calendar */ "Default calendar :" = "Default calendar:"; "selectedCalendar" = "Selected calendar"; diff --git a/UI/PreferencesUI/Finnish.lproj/Localizable.strings b/UI/PreferencesUI/Finnish.lproj/Localizable.strings index 08a58f9d5..1224ac125 100644 --- a/UI/PreferencesUI/Finnish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Finnish.lproj/Localizable.strings @@ -104,6 +104,10 @@ "firstWeekOfYear_First4DayWeek" = "Ensimmäinen nelipäiväinen viikko"; "firstWeekOfYear_FirstFullWeek" = "Ensimmäinen täysi viikko"; +"Prevent from being invited to appointments" = "Estä tapaamiskutsut"; +"White list for appointment invitations:" = "Valkoinen lista kalenterikutsuille:"; +"Contacts Names" = "Yhteystietojen Nimet"; + /* Default Calendar */ "Default calendar :" = "Oletuskalenteri"; "selectedCalendar" = "Valittu kalenteri"; diff --git a/UI/PreferencesUI/French.lproj/Localizable.strings b/UI/PreferencesUI/French.lproj/Localizable.strings index b4d291e61..23c323376 100644 --- a/UI/PreferencesUI/French.lproj/Localizable.strings +++ b/UI/PreferencesUI/French.lproj/Localizable.strings @@ -104,6 +104,10 @@ "firstWeekOfYear_First4DayWeek" = "Première semaine de 4 jours"; "firstWeekOfYear_FirstFullWeek" = "Première semaine entière"; +"Prevent from being invited to appointments" = "Interdire de m'inviter à des rendez-vous"; +"White list for appointment invitations:" = "Liste blanche pour les invitations"; +"Contacts Names" = "Noms des contacts"; + /* Default Calendar */ "Default calendar :" = "Calendrier par défaut :"; "selectedCalendar" = "le calendrier sélectionné"; diff --git a/UI/PreferencesUI/German.lproj/Localizable.strings b/UI/PreferencesUI/German.lproj/Localizable.strings index 41abe64a3..393e5e883 100644 --- a/UI/PreferencesUI/German.lproj/Localizable.strings +++ b/UI/PreferencesUI/German.lproj/Localizable.strings @@ -22,7 +22,7 @@ /* vacation (auto-reply) */ "Enable vacation auto reply" = "Automatische Abwesenheitsnachricht aktivieren"; -"Auto reply message :" = "Folgende Nachricht wird automatisch an jeden Absender gesendet:"; +"Auto reply message :" = "Mit folgender Nachricht auf jede eingehende E-Mail antworten:"; "Email addresses (separated by commas) :" = "E-Mail-Adressen (getrennt durch Kommata):"; "Add default email addresses" = "Standardadresse hinzufügen"; "Days between responses :" = "Tage zwischen automatischen Abwesenheitsnachrichten:"; @@ -104,6 +104,10 @@ "firstWeekOfYear_First4DayWeek" = "Erste 4 Tage Woche"; "firstWeekOfYear_FirstFullWeek" = "Erste ganze Woche"; +"Prevent from being invited to appointments" = "Verhindere, dass ich zu Terminen eingeladen werde"; +"White list for appointment invitations:" = "White-Liste für Termineinladungen:"; +"Contacts Names" = "Kontaktnamen"; + /* Default Calendar */ "Default calendar :" = "Standardkalender"; "selectedCalendar" = "Gewählter Kalender"; @@ -129,7 +133,7 @@ "Label" = "Schlagwort"; "Show subscribed mailboxes only" = "Nur abonnierte Ordner anzeigen"; "Sort messages by threads" = "Nachrichten nach Thema sortieren"; -"When sending mail, add unknown recipients to my" = "Wenn ich E-Mails sende, füge die unbekannten Empfänger zu meinem"; +"When sending mail, add unknown recipients to my" = "Unbekannte Empfänger meiner E-Mails hinzufügen zu"; "Check for new mail:" = "Auf neue Nachrichten prüfen:"; "messagecheck_manually" = "Manuell"; "messagecheck_every_minute" = "Minütlich"; @@ -310,7 +314,7 @@ "Password change failed - Password is too short" = "Passwortänderung fehlgeschlagen - Passwort ist zu kurz"; "Password change failed - Password is too young" = "Passwortänderung fehlgeschlagen - zu frühe Passwortänderung"; "Password change failed - Password is in history" = "Passwortänderung fehlgeschlagen - Passwort wurde zuvor verwendet"; -"Unhandled policy error: %{0}" = "Unbearbeiteter Vorgabenfehler: %{0}"; -"Unhandled error response" = "Unbearbeitete Fehlerantwort"; +"Unhandled policy error: %{0}" = "Unerwarteter Fehler bei Passwortänderung: %{0}"; +"Unhandled error response" = "Unerwarteter Fehler"; "Password change is not supported." = "Passwortänderung wird nicht unterstützt."; -"Unhandled HTTP error code: %{0}" = "Unbearbeiteter HTTP-Fehlercode: %{0}"; +"Unhandled HTTP error code: %{0}" = "Unerwarteter HTTP-Fehlercode: %{0}"; diff --git a/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings b/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings index a0abeb7aa..4dc692f4a 100644 --- a/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings +++ b/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings @@ -104,6 +104,10 @@ "firstWeekOfYear_First4DayWeek" = "Primera semana del año de 4 días"; "firstWeekOfYear_FirstFullWeek" = "Primera semana del año completa"; +"Prevent from being invited to appointments" = "Evitar invitaciones a eventos"; +"White list for appointment invitations:" = "Lista blanca de invitaciones a eventos:"; +"Contacts Names" = "Nombres de Contacto"; + /* Default Calendar */ "Default calendar :" = "Calendario por defecto"; "selectedCalendar" = "Calendario seleccionado"; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 0900c4d3a..ffd641912 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -19,6 +19,7 @@ */ #import +#import #import #import #import @@ -41,6 +42,7 @@ #import #import #import +#import #import #import #import @@ -638,6 +640,47 @@ static NSArray *reminderValues = nil; return [userDefaults busyOffHours]; } +- (NSArray *) whiteList +{ + SOGoUserSettings *us; + NSMutableDictionary *moduleSettings; + NSArray *whiteList; + + us = [user userSettings]; + moduleSettings = [us objectForKey: @"Calendar"]; + whiteList = [moduleSettings objectForKey:@"PreventInvitationsWhitelist"]; + return whiteList; +} + +- (void) setWhiteList: (NSArray *) whiteList +{ + SOGoUserSettings *us; + NSMutableDictionary *moduleSettings; + us = [user userSettings]; + moduleSettings = [us objectForKey: @"Calendar"]; + [moduleSettings setObject: whiteList forKey: @"PreventInvitationsWhitelist"]; + [us synchronize]; +} + +- (void) setPreventInvitations: (BOOL) preventInvitations +{ + SOGoUserSettings *us; + NSMutableDictionary *moduleSettings; + us = [user userSettings]; + moduleSettings = [us objectForKey: @"Calendar"]; + [moduleSettings setObject: [NSNumber numberWithBool: preventInvitations] forKey: @"PreventInvitations"]; + [us synchronize]; +} + +- (BOOL) preventInvitations +{ + SOGoUserSettings *us; + NSMutableDictionary *moduleSettings; + us = [user userSettings]; + moduleSettings = [us objectForKey: @"Calendar"]; + return [[moduleSettings objectForKey: @"PreventInvitations"] boolValue]; +} + - (NSArray *) firstWeekList { return [NSArray arrayWithObjects: diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index ecb4a9135..5ee1b3bd9 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -314,7 +314,6 @@ "Repeat" = "Repeat"; "Daily" = "Daily"; -"Multi-Columns" = "Multi-Columns"; "Weekly" = "Weekly"; "Monthly" = "Monthly"; "Yearly" = "Yearly"; diff --git a/UI/Scheduler/Finnish.lproj/Localizable.strings b/UI/Scheduler/Finnish.lproj/Localizable.strings index 83a4c7f03..8c1209232 100644 --- a/UI/Scheduler/Finnish.lproj/Localizable.strings +++ b/UI/Scheduler/Finnish.lproj/Localizable.strings @@ -155,6 +155,7 @@ "What to Print" = "Tulostusvalinnat"; "Options" = "Asetukset"; "Tasks with no due date" = "Tehtävät ilman määräpäivää"; +"Display working hours only" = "Näytä vain työajat"; "Completed tasks" = "Valmistuneet tehtävät"; /* Appointments */ @@ -542,6 +543,8 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)"; "tagWasRemoved" = "Mobiililaitteesi kalenteritiedot on päivitettävä mikäli haluat poistaa tämän kalenterin synkronoinnista.⏎ Jatketaanko?"; "DestinationCalendarError" = "Lähde- ja kohdekalenterit ovat samoja. Ole hyvä ja yritä kopiointia toiseen kalenteriin."; "EventCopyError" = "Kopiointi epäonnistui. Ole hyvä ja yritä kopiointia toiseen kalenteriin."; +"Please select at least one calendar" = "Ole hyvä ja valitse ainakin yksi kalenteri"; + "Open Task..." = "Avaa tehtävä..."; "Mark Completed" = "Merkitse valmistuneeksi"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 42cdb99f7..ebe4514cd 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -155,6 +155,7 @@ "What to Print" = "Contenu"; "Options" = "Options"; "Tasks with no due date" = "Tâches sans date d'échéance"; +"Display working hours only" = "Afficher les heures de bureau seulement"; "Completed tasks" = "Tâches complétées"; /* Appointments */ @@ -542,6 +543,8 @@ vtodo_class2 = "(Tâche confidentielle)"; "tagWasRemoved" = "Afin de ne plus synchroniser ce calendrier, vous devrez recharger les données sur votre appareil mobile.\nVoulez-vous continuer?"; "DestinationCalendarError" = "Le calendrier de destination est le même que celui de l'événement. Choisissez un calendrier de destination différent."; "EventCopyError" = "La copie a échouée. Choisissez un calendrier de destination différent."; +"Please select at least one calendar" = "Veuillez sélectionner au moins un calendrier."; + "Open Task..." = "Ouvrir la tâche..."; "Mark Completed" = "Marquer comme accomplie"; diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index 63a9fdce0..bec5f0bed 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -155,6 +155,7 @@ "What to Print" = "Was soll gedruckt werden"; "Options" = "Optionen"; "Tasks with no due date" = "Aufgaben ohne Fälligkeitsdatum"; +"Display working hours only" = "Nur Arbeitszeit anzeigen"; "Completed tasks" = "Abgeschlossene Aufgaben"; /* Appointments */ @@ -542,6 +543,8 @@ vtodo_class2 = "(Vertrauliche Aufgabe)"; "tagWasRemoved" = "Wenn der Kalender aus der Synchronisation entfernt wird, müssen die Daten erneut auf das Mobilgerät geladen werden.\nFortfahren?"; "DestinationCalendarError" = "Quell- und Zielkalender sind identisch. Bitte kopieren Sie in einen anderen Kalender."; "EventCopyError" = "Kopieren fehlgeschlagen. Bitte kopieren Sie in einen anderen Kalender."; +"Please select at least one calendar" = "Bitte mindestens einen Kalender auswählen"; + "Open Task..." = "Aufgabe öffnen..."; "Mark Completed" = "Als abgeschlossen markieren"; diff --git a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings index 7756be8d8..6e03471c8 100644 --- a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings @@ -155,6 +155,7 @@ "What to Print" = "Que imprimir"; "Options" = "Parametros"; "Tasks with no due date" = "Tareas sin fecha de vencimiento"; +"Display working hours only" = "Enseñar solo horas de trabajo"; "Completed tasks" = "Tareas Finalizadas"; /* Appointments */ @@ -542,6 +543,8 @@ vtodo_class2 = "(Tarea confidencial)"; "tagWasRemoved" = "Si quita este calendario de la sincronización, necesitará recargar los datos en su teléfono móvil.\n¿Continuar?"; "DestinationCalendarError" = "El calendario origen y el destino son iguales. Por favor, intente copiar a otro calendario."; "EventCopyError" = "La copia falló. Por favor, intente copiar a un calendario distinto."; +"Please select at least one calendar" = "Por favor, selecciona al menos un calendario"; + "Open Task..." = "Abrir tarea..."; "Mark Completed" = "Marcar como completo"; diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar index 14e5fcd5e..58bae28ab 100644 --- a/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +++ b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar @@ -22,7 +22,7 @@ image = "day-view.png"; tooltip = "Switch to day view"; }, { link = "#"; - label="Multi-Columns View"; + label="Multicolumn Day View"; onclick = "return onMulticolumnDayOverview();"; image = "day-view-multicolumn.png"; tooltip = "Switch to multi-columns day view"; }, diff --git a/UI/Scheduler/UIxAppointmentActions.m b/UI/Scheduler/UIxAppointmentActions.m index db6d55c91..05399f76b 100644 --- a/UI/Scheduler/UIxAppointmentActions.m +++ b/UI/Scheduler/UIxAppointmentActions.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -52,19 +53,25 @@ WOResponse *response; WORequest *rq; SOGoAppointmentObject *co; + SoSecurityManager *sm; iCalEvent *event; NSCalendarDate *start, *newStart, *end, *newEnd; NSTimeInterval newDuration; SOGoUserDefaults *ud; - NSString *daysDelta, *startDelta, *durationDelta; + NSString *daysDelta, *startDelta, *durationDelta, *destionationCalendar; + NSArray *calendarsID; NSTimeZone *tz; NSException *ex; + SOGoAppointmentFolder *targetCalendar, *sourceCalendar; + SOGoAppointmentFolders *folders; rq = [context request]; daysDelta = [rq formValueForKey: @"days"]; startDelta = [rq formValueForKey: @"start"]; durationDelta = [rq formValueForKey: @"duration"]; + destionationCalendar = [rq formValueForKey: @"destination"]; + if ([daysDelta length] > 0 || [startDelta length] > 0 || [durationDelta length] > 0) { @@ -105,10 +112,30 @@ } if ([event hasRecurrenceRules]) - [event updateRecurrenceRulesUntilDate: end]; + [event updateRecurrenceRulesUntilDate: end]; [event setLastModified: [NSCalendarDate calendarDate]]; ex = [co saveComponent: event]; + // This condition will be executed only if the event is moved from a calendar to another. If destionationCalendar == 0; there is no calendar change + if (![destionationCalendar isEqualToString:@"0"]) + { + folders = [[self->context activeUser] calendarsFolderInContext: self->context]; + sourceCalendar = [co container]; + targetCalendar = [folders lookupName:[destionationCalendar stringValue] inContext: self->context acquire: 0]; + // The event was moved to a different calendar. + sm = [SoSecurityManager sharedSecurityManager]; + if (![sm validatePermission: SoPerm_DeleteObjects + onObject: sourceCalendar + inContext: context]) + { + if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles + onObject: targetCalendar + inContext: context]) + ex = [co moveToFolder: targetCalendar]; + } + + + } if (ex) { NSDictionary *jsonResponse; diff --git a/UI/Scheduler/UIxCalDayTable.m b/UI/Scheduler/UIxCalDayTable.m index 799d55f57..7cfde1b1f 100644 --- a/UI/Scheduler/UIxCalDayTable.m +++ b/UI/Scheduler/UIxCalDayTable.m @@ -191,7 +191,7 @@ NSMutableDictionary *calendar; unsigned int count, foldersCount; NSString *folderName, *fDisplayName; - NSNumber *isActive; + BOOL *isActive; co = [self clientObject]; folders = [co subFolders]; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 607dc9062..70964eac7 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -75,8 +75,6 @@ static NSArray *tasksFields = nil; #define maxBlocks (offsetBlocks * 2) // maximum number of blocks to search // for a free slot (10 days) -@class SOGoAppointment; - @implementation UIxCalListingActions + (void) initialize diff --git a/UI/Scheduler/UIxCalViewPrint.m b/UI/Scheduler/UIxCalViewPrint.m index d186ef4fa..eb432c02a 100644 --- a/UI/Scheduler/UIxCalViewPrint.m +++ b/UI/Scheduler/UIxCalViewPrint.m @@ -2,7 +2,6 @@ * * Copyright (C) 2006-2014 Inverse inc. * - * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) @@ -38,7 +37,6 @@ static NSArray *layoutItems = nil; layoutItems = [NSArray arrayWithObjects: @"LIST", @"Daily", @"Multi-Columns", @"Weekly", nil]; [layoutItems retain]; } - } - (id) init @@ -53,12 +51,9 @@ static NSArray *layoutItems = nil; [item release]; } -/****************************************************************/ -/* Interfacing; populating the popup list for the print layouts */ - - (void) setItem: (NSString *) newItem { - ASSIGN (item, newItem); + ASSIGN(item, newItem); } - (NSString *) item @@ -76,9 +71,12 @@ static NSArray *layoutItems = nil; return [self labelForKey: [NSString stringWithFormat: item]]; } +// +// The objective here is to return the parent view layout and select the print +// layout corresponding. Default print view: list view +// - (NSString *) parentPrintLayout { - // The objective here is to return the parent view layout and select the print layout corresponding. Default print view: list view SOGoUser *activeUser; NSString *parentView; @@ -99,7 +97,4 @@ static NSArray *layoutItems = nil; return @"LIST"; } -/******************************************************************/ -/* */ - @end diff --git a/UI/Scheduler/UIxCalendarSelector.m b/UI/Scheduler/UIxCalendarSelector.m index 2c8a6910d..c81587116 100644 --- a/UI/Scheduler/UIxCalendarSelector.m +++ b/UI/Scheduler/UIxCalendarSelector.m @@ -121,7 +121,8 @@ _intValueFromHex (NSString *hexString) [calendar setObject: [folder ownerInContext: context] forKey: @"owner"]; fActiveTasks = [folder activeTasks]; - [calendar setObject:fActiveTasks forKey:@"activeTasks" ]; + if (fActiveTasks > 0) + [calendar setObject: fActiveTasks forKey:@"activeTasks" ]; [calendars addObject: calendar]; } } diff --git a/UI/Scheduler/UIxDatePicker.h b/UI/Scheduler/UIxDatePicker.h index dcfd2cf46..cc30eae69 100644 --- a/UI/Scheduler/UIxDatePicker.h +++ b/UI/Scheduler/UIxDatePicker.h @@ -1,8 +1,6 @@ /* UIxDatePicker.h - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. - * - * Author: Francis Lachapelle + * Copyright (C) 2009-2014 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/UI/Scheduler/UIxDatePicker.m b/UI/Scheduler/UIxDatePicker.m index f0618a744..921736814 100644 --- a/UI/Scheduler/UIxDatePicker.m +++ b/UI/Scheduler/UIxDatePicker.m @@ -1,23 +1,22 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo 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 - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ +/* UIxDatePicker.h - this file is part of SOGo + * + * Copyright (C) 2009-2014 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ #import #import @@ -38,13 +37,18 @@ { if ((self = [super init])) { + dateID = nil; + day = nil; + year = nil; + label = nil; isDisabled = NO; } return self; } -- (void)dealloc { +- (void) dealloc +{ [self->dateID release]; [self->day release]; [self->month release]; @@ -55,60 +59,79 @@ /* Accessors */ -- (void)setDateID:(NSString *)_dateID { +- (void) setDateID: (NSString *) _dateID +{ ASSIGNCOPY(self->dateID, _dateID); } -- (NSString *)dateID { +- (NSString *) dateID +{ return self->dateID; } -- (void)setDay:(id)_day { +- (void) setDay: (id) _day +{ ASSIGN(self->day, _day); } -- (id)day { - return self->day; +- (id) day +{ + return self->day; } -- (void)setMonth:(id)_month { + +- (void) setMonth: (id) _month +{ ASSIGN(self->month, _month); } -- (id)month { - return self->month; +- (id) month +{ + return self->month; } -- (void)setYear:(id)_year { + +- (void) setYear: (id) _year +{ ASSIGN(self->year, _year); } -- (id)year { - return self->year; +- (id) year +{ + return self->year; } - -- (void)setLabel:(NSString *)_label { +- (void) setLabel: (NSString *) _label +{ ASSIGNCOPY(self->label, _label); } -- (NSString *)label { +- (NSString *) label +{ return self->label; } /* formats */ -- (BOOL)useISOFormats { +- (BOOL) useISOFormats +{ + NSNumber *useISOFormats; WOContext *ctx; - NSNumber *useISOFormats; - ctx = [self context]; + ctx = [self context]; useISOFormats = [ctx valueForKey:@"useISOFormats"]; - if (!useISOFormats) { - NSArray *languages = [ctx resourceLookupLanguages]; - if (languages && [languages count] > 0) { - if ([[languages objectAtIndex:0] isEqualToString:@"French"]) { - useISOFormats = [NSNumber numberWithBool:NO]; + if (!useISOFormats) + { + NSArray *languages; + + languages = [ctx resourceLookupLanguages]; + if (languages && [languages count] > 0) + { + if ([[languages objectAtIndex:0] isEqualToString:@"French"]) + { + useISOFormats = [NSNumber numberWithBool:NO]; + } } - } if (!useISOFormats) - useISOFormats = [NSNumber numberWithBool:YES]; - [ctx takeValue:useISOFormats forKey:@"useISOFormats"]; - } + useISOFormats = [NSNumber numberWithBool: YES]; + + [ctx takeValue: useISOFormats forKey:@"useISOFormats"]; + } + return [useISOFormats boolValue]; } @@ -116,17 +139,19 @@ { char buf[22]; - if ([self useISOFormats]) { - sprintf(buf, "%04d-%02d-%02d", - [[self year] intValue], - [[self month] intValue], - [[self day] intValue]); - } - else { - sprintf(buf, "%02d/%02d/%04d", - [[self day] intValue], - [[self month] intValue], - [[self year] intValue]); + if ([self useISOFormats]) + { + sprintf(buf, "%04d-%02d-%02d", + [[self year] intValue], + [[self month] intValue], + [[self day] intValue]); + } + else + { + sprintf(buf, "%02d/%02d/%04d", + [[self day] intValue], + [[self month] intValue], + [[self year] intValue]); } return [NSString stringWithCString:buf]; } @@ -141,29 +166,30 @@ return [self useISOFormats] ? @"yyyy-mm-dd" : @"dd/mm/yyyy"; } -/* action */ - - (void) takeValuesFromRequest: (WORequest *) _rq - inContext: (WOContext *)_ctx + inContext: (WOContext *) _ctx { - NSString *dateString; - NSCalendarDate *d; NSInteger dateTZOffset, userTZOffset; NSTimeZone *systemTZ, *userTZ; SOGoUserDefaults *ud; + NSString *dateString; + NSCalendarDate *d; dateString = [_rq formValueForKey:[self dateID]]; - if (dateString == nil) { - [self debugWithFormat:@"got no date string!"]; - return; - } + + if (dateString == nil) + { + [self debugWithFormat:@"got no date string!"]; + return; + } - d = [NSCalendarDate dateWithString:dateString - calendarFormat:[self dateFormat]]; - if (d == nil) { - [self warnWithFormat:@"Could not parse dateString: '%@'", + d = [NSCalendarDate dateWithString: dateString + calendarFormat: [self dateFormat]]; + if (!d) + { + [self warnWithFormat: @"Could not parse dateString: '%@'", dateString]; - } + } /* we must adjust the date timezone because "dateWithString:..." uses the system timezone, which can be different from the user's. */ @@ -178,11 +204,11 @@ seconds: (dateTZOffset - userTZOffset)]; [d setTimeZone: userTZ]; - [self setDay: [NSNumber numberWithInt:[d dayOfMonth]]]; - [self setMonth:[NSNumber numberWithInt:[d monthOfYear]]]; + [self setDay: [NSNumber numberWithInt:[d dayOfMonth]]]; + [self setMonth: [NSNumber numberWithInt:[d monthOfYear]]]; [self setYear: [NSNumber numberWithInt:[d yearOfCommonEra]]]; - [super takeValuesFromRequest:_rq inContext:_ctx]; + [super takeValuesFromRequest: _rq inContext: _ctx]; } - (void) setDisabled: (BOOL) disabled diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 9ee97a9ee..2f8a4c415 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -110,7 +110,7 @@ var:class="currentContactFolderClass" var:acl-editing="currentContactFolderAclEditing" var:list-editing="currentContactFolderListEditing" - > diff --git a/UI/Templates/PreferencesUI/UIxPreferences.wox b/UI/Templates/PreferencesUI/UIxPreferences.wox index 9cc6675a2..cd72b338f 100644 --- a/UI/Templates/PreferencesUI/UIxPreferences.wox +++ b/UI/Templates/PreferencesUI/UIxPreferences.wox @@ -11,12 +11,15 @@ title="title" const:popup="YES" const:cssFiles="datepicker.css" - const:jsFiles="RowEditionController.js,PasswordPolicy.js,ckeditor/ckeditor.js,datepicker.js" + const:jsFiles="RowEditionController.js,PasswordPolicy.js,ckeditor/ckeditor.js,datepicker.js, SOGoAutoCompletion.js" > - +
+
    +
    + - +
    • -
    • - + +
    • -
    • + label:value="Calendar Options"/> +
    • + label:value="Mail Options"/>
    • + label:value="IMAP Accounts"/>
    • -
      + label:value="Vacation"/>
    • +
    • -
    • -
      + +
    @@ -128,38 +131,38 @@
    + const:id="language" + const:name="language" + string="languageText" + selection="language" + label:noSelectionString="choose" />
    + const:id="timezone" + const:name="timezone" + string="item" selection="userTimeZone" />
    + const:id="shortDateFormat" + const:name="shortDateFormat" + string="itemShortDateFormatText" selection="userShortDateFormat"/>
    + const:id="longDateFormat" + const:name="longDateFormat" + string="itemLongDateFormatText" selection="userLongDateFormat" + />
    + const:id="timeFormat" + const:name="timeFormat" + string="itemTimeFormatText" selection="userTimeFormat" + />
    + const:id="defaultModule" + const:name="defaultModule" + string="itemModuleText" selection="userDefaultModule"/>
    @@ -167,88 +170,132 @@
    + const:id="weekStartDay" + const:name="weekStartDay" + string="itemWeekStartDay" selection="userWeekStartDay"/>
    + const:id="dayStartTime" + const:name="dayStartTime" + string="item" selection="userDayStartTime"/>
    + const:id="dayEndTime" + const:name="dayEndTime" + string="item" selection="userDayEndTime"/>
    -
    +
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    - - -
    - - - - - - - -
    -
    - - - +
    +
      +
    • +
    • +
    • +
    • +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    + +
    + +
    +
    +
    + + + + +
    + +
    + +
    +
    +
    +
    + +
    + + + + + + + + + + + + + +
    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    @@ -258,14 +305,14 @@ + > + > @@ -278,135 +325,135 @@
    - +
    -
    -
    -
    -
    -
    -
    -
    + const:name="subscribedFoldersOnly" + const:id="subscribedFoldersOnly" + var:checked="showSubscribedFoldersOnly" /> + +
    +
    +
    +
    +
    +
    + const:id="messageCheck" + const:name="messageCheck" + string="itemMessageCheckText" selection="userMessageCheck"/>
    + const:id="messageForwarding" + const:name="messageForwarding" + string="itemMessageForwardingText" + selection="userMessageForwarding"/>
    + const:id="replyPlacementList" + const:name="replyPlacementList" + string="itemReplyPlacementText" + selection="userReplyPlacement"/>
    + const:id="signaturePlacementList" + const:name="signaturePlacementList" + string="itemSignaturePlacementText" + selection="userSignaturePlacement"/>
    + const:id="composeMessagesType" + const:name="composeMessagesType" + string="itemComposeMessagesText" + selection="userComposeMessagesType"/>
    + const:id="displayRemoteInlineImages" + const:name="displayRemoteInlineImages" + string="itemDisplayRemoteInlineImagesText" + selection="userDisplayRemoteInlineImages"/>
    - +
    • + >
    • + label:value="Labels"/>
    - -
    - - - - + + +
    + +
    + + + + - - - - -
    - -
    - -
    - +
    + +
    + +
    +
    + > + > - + - - + +
    -
    -
    +
    +
    @@ -414,33 +461,33 @@ + const:name="mailLabelsValue" var:value="mailLabelsValue"/>
    - +
    + var:value="mailAccounts"/>
    + >
    @@ -448,15 +495,15 @@
    - -
    + +
    - - - - -
    + + + + +
    @@ -464,11 +511,11 @@
    - + - +
    @@ -481,40 +528,40 @@
    - +

    + label:value="When I receive a request for a return receipt:" + />

    + label:value="Never send a return receipt"/>

    + label:value="Allow return receipts for some messages"/>
    + label:value="If I'm not in the To or Cc of the message:"/>
    - + + label:value="If the sender is outside my domain:"/>
    - + + label:value="In all other cases:"/> +
    ",CKEDITOR.document); -a.insertAfter(c);c.hide();c.$.form&&b._attachToForm()}else b.setData(a.getHtml(),null,true);b.on("loaded",function(){b.fire("uiReady");b.editable(a);b.container=a;b.setData(b.getData(1));b.resetDirty();b.fire("contentDom");b.mode="wysiwyg";b.fire("mode");b.status="ready";b.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,b)},null,null,1E4);b.on("destroy",function(){if(c){b.container.clearCustomData();b.container.remove();c.show()}b.element.clearCustomData();delete b.element});return b}; -CKEDITOR.inlineAll=function(){var a,e,b;for(b in CKEDITOR.dtd.$editable)for(var c=CKEDITOR.document.getElementsByTag(b),d=0,f=c.count();d{voiceLabel}<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation">{bottomHtml}')); -b=CKEDITOR.dom.element.createFromHtml(c.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:[a.lang.editor,a.name].join(", "),topHtml:i?''+i+"":"",contentId:a.ui.spaceId("contents"),bottomHtml:k?''+k+"":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(n==CKEDITOR.ELEMENT_MODE_REPLACE){e.hide(); -b.insertAfter(e)}else e.append(b);a.container=b;i&&a.ui.space("top").unselectable();k&&a.ui.space("bottom").unselectable();e=a.config.width;n=a.config.height;e&&b.setStyle("width",CKEDITOR.tools.cssLength(e));n&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(n));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(b,c){return a(b,c,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(b,c,e){return a(b, -c,e,CKEDITOR.ELEMENT_MODE_APPENDTO)};CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b",m="",a=f+a.replace(e,function(){return m+f})+m}a=a.replace(/\n/g,"
    ");b||(a=a.replace(RegExp("
    (?=)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g," ");a=a.replace(/(>|\s) /g,function(a,b){return b+" "}).replace(/ (?=<)/g," ");r(this,"text",a)},insertElement:function(a,b){b?this.insertElementIntoRange(a,b):this.insertElementIntoSelection(a)},insertElementIntoRange:function(a, -b){var c=this.editor,d=c.config.enterMode,e=a.getName(),f=CKEDITOR.dtd.$block[e];if(b.checkReadOnly())return false;b.deleteContents(1);b.startContainer.type==CKEDITOR.NODE_ELEMENT&&b.startContainer.is({tr:1,table:1,tbody:1,thead:1,tfoot:1})&&v(b);var m,h;if(f)for(;(m=b.getCommonAncestor(0,1))&&(h=CKEDITOR.dtd[m.getName()])&&(!h||!h[e]);)if(m.getName()in CKEDITOR.dtd.span)b.splitElement(m);else if(b.checkStartOfBlock()&&b.checkEndOfBlock()){b.setStartBefore(m);b.collapse(true);m.remove()}else b.splitBlock(d== -CKEDITOR.ENTER_DIV?"div":"p",c.editable());b.insertNode(a);return true},insertElementIntoSelection:function(a){h(this);var b=this.editor,d=b.activeEnterMode,b=b.getSelection(),e=b.getRanges()[0],f=a.getName(),f=CKEDITOR.dtd.$block[f];if(this.insertElementIntoRange(a,e)){e.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);if(f)if((f=a.getNext(function(a){return c(a)&&!i(a)}))&&f.type==CKEDITOR.NODE_ELEMENT&&f.is(CKEDITOR.dtd.$block))f.getDtd()["#"]?e.moveToElementEditStart(f):e.moveToElementEditEnd(a); -else if(!f&&d!=CKEDITOR.ENTER_BR){f=e.fixBlock(true,d==CKEDITOR.ENTER_DIV?"div":"p");e.moveToElementEditStart(f)}}b.selectRanges([e]);n(this)},setData:function(a,b){b||(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);if(this.status=="unloaded")this.status="ready";this.editor.fire("dataReady")},getData:function(a){var b=this.getHtml();a||(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable",!a)},detach:function(){this.removeClass("cke_editable"); -this.status="detached";var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||a.config.ignoreEmptyParagraph!==false&&(b=b.replace(k,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData", -function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)}, -this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE||a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus=false},null,null,-1);this.on("focus",function(){this.hasFocus= -true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus=true;a.once("contentDom",function(){a.focusManager.focus()})}this.isInline()&&this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var d=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var e=a.config.contentsLangDirection;this.getDirection(1)!=e&&this.changeAttr("dir",e);var h=CKEDITOR.getCss();if(h){e= -d.getHead();if(!e.getCustomData("stylesheet")){h=d.appendStyleText(h);h=new CKEDITOR.dom.element(h.ownerNode||h.owningElement);e.setCustomData("stylesheet",h);h.data("cke-temp",1)}}e=d.getCustomData("stylesheet_ref")||0;d.setCustomData("stylesheet_ref",e+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()}); -var l={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.domEvent.getKey(),d;if(c in l){var b=a.getSelection(),e,q=b.getRanges()[0],h=q.startPath(),i,k,p,c=c==8;if(CKEDITOR.env.ie&&CKEDITOR.env.version<11&&(e=b.getSelectedElement())||(e=f(b))){a.fire("saveSnapshot");q.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START);e.remove();q.select();a.fire("saveSnapshot");d=1}else if(q.collapsed)if((i=h.block)&&(p=i[c?"getPrevious":"getNext"](o))&&p.type==CKEDITOR.NODE_ELEMENT&& -p.is("table")&&q[c?"checkStartOfBlock":"checkEndOfBlock"]()){a.fire("saveSnapshot");q[c?"checkEndOfBlock":"checkStartOfBlock"]()&&i.remove();q["moveToElementEdit"+(c?"End":"Start")](p);q.select();a.fire("saveSnapshot");d=1}else if(h.blockLimit&&h.blockLimit.is("td")&&(k=h.blockLimit.getAscendant("table"))&&q.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(p=k[c?"getPrevious":"getNext"](o))){a.fire("saveSnapshot");q["moveToElementEdit"+(c?"End":"Start")](p);q.checkStartOfBlock()&&q.checkEndOfBlock()? -p.remove():q.select();a.fire("saveSnapshot");d=1}else if((k=h.contains(["td","th","caption"]))&&q.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END))d=1}return!d});a.blockless&&(CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)&&this.attachListener(this,"keyup",function(b){if(b.data.getKeystroke()in l&&!this.getFirst(c)){this.appendBogus();b=a.createRange();b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START);b.select()}});this.attachListener(this,"dblclick",function(b){if(a.readOnly)return false; -b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie||this.attachListener(this,"mousedown",function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")&&!c.isReadOnly()){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button==2){b=b.data.getTarget();if(!b.getOuterHtml().replace(k, -"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()});this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()})}CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){b=b.data.domEvent.getKey();if(b in l){var b=b==8,c=a.getSelection(),d=c.getRanges()[0],e=d.startPath(),f=e.block;if(d.collapsed&& -f&&d[b?"checkStartOfBlock":"checkEndOfBlock"]()&&d.moveToClosestEditablePosition(f,!b)&&d.collapsed){if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var h=d.startContainer.getChild(d.startOffset-(b?1:0));if(h&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("hr")){a.fire("saveSnapshot");h.remove();a.fire("saveSnapshot");return false}}if((d=d.startPath().block)&&(!d||!d.contains(f))){a.fire("saveSnapshot");for(var i=f.getCommonAncestor(d),k=b?f:d,h=k;(k=k.getParent())&&!i.equals(k)&&k.getChildCount()==1;)h=k; -var p;(p=(b?d:f).getBogus())&&p.remove();p=c.createBookmarks();(b?f:d).moveChildren(b?d:f,false);e.lastElement.mergeSiblings();h.remove();c.selectBookmarks(p);a.fire("saveSnapshot");return false}}}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c= -a.getCustomData("stylesheet_ref");if(--c)a.setCustomData("stylesheet_ref",c);else{a.removeCustomData("stylesheet_ref");b.removeCustomData("stylesheet").remove()}}}this.editor.fire("contentDomUnload");delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var i=CKEDITOR.dom.walker.bogus(),k=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi, -o=CKEDITOR.dom.walker.whitespaces(true),s=CKEDITOR.dom.walker.bookmark(false,true);CKEDITOR.on("instanceLoaded",function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty(); -c.fire("lockSnapshot");a(b);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})});CKEDITOR.on("instanceCreated",function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);if(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents")){var d=CKEDITOR.tools.getNextId(),e=CKEDITOR.dom.element.createFromHtml(''+this.lang.common.editorHelp+ -"");c.append(e);a.changeAttr("aria-describedby",d)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");var r=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,d){var e,f,j,l,t=[],q=d.range.startContainer;e=d.range.startPath();for(var q=m[q.getName()],h=0,k=c.getChildren(),i=k.count(),u=-1,o=-1,n=0,s=e.contains(m.$list);h-1)t[u].firstNotAllowed=1;if(o>-1)t[o].lastNotAllowed=1;return t}function d(b,c){var e=[],f=b.getChildren(),j=f.count(),l,t=0,q=m[c],h=!b.is(m.$inline)|| -b.is("br");for(h&&e.push(" ");t ",o.document);o.insertNode(x); -o.setStartAfter(x)}y=new CKEDITOR.dom.elementPath(o.startContainer);i.endPath=B=new CKEDITOR.dom.elementPath(o.endContainer);if(!o.collapsed){var t=B.block||B.blockLimit,N=o.getCommonAncestor();t&&(!t.equals(N)&&!t.contains(N)&&o.checkEndOfBlock())&&i.zombies.push(t);o.deleteContents()}for(;(w=a(o.startContainer)&&o.startContainer.getChild(o.startOffset-1))&&a(w)&&w.isBlockBoundary()&&y.contains(w);)o.moveToPosition(w,CKEDITOR.POSITION_BEFORE_END);f(o,i.blockLimit,y,B);if(x){o.setEndBefore(x);o.collapse(); -x.remove()}x=o.startPath();if(t=x.contains(e,false,1)){o.splitElement(t);i.inlineStylesRoot=t;i.inlineStylesPeak=x.lastElement}x=o.createBookmark();(t=x.startNode.getPrevious(c))&&a(t)&&e(t)&&r.push(t);(t=x.startNode.getNext(c))&&a(t)&&e(t)&&r.push(t);for(t=x.startNode;(t=t.getParent())&&e(t);)r.push(t);o.moveToBookmark(x);if(x=u){x=i.range;if(i.type=="text"&&i.inlineStylesRoot){w=i.inlineStylesPeak;o=w.getDocument().createText("{cke-peak}");for(r=i.inlineStylesRoot.getParent();!w.equals(r);){o=o.appendTo(w.clone()); -w=w.getParent()}u=o.getOuterHtml().split("{cke-peak}").join(u)}w=i.blockLimit.getName();if(/^\s+|\s+$/.test(u)&&"span"in CKEDITOR.dtd[w])var M=' ',u=M+u+M;u=i.editor.dataProcessor.toHtml(u,{context:null,fixForBody:false,dontFilter:i.dontFilter,filter:i.editor.activeFilter,enterMode:i.editor.activeEnterMode});w=x.document.createElement("body");w.setHtml(u);if(M){w.getFirst().remove();w.getLast().remove()}if((M=x.startPath().block)&&!(M.getChildCount()==1&&M.getBogus()))a:{var D; -if(w.getChildCount()==1&&a(D=w.getFirst())&&D.is(k)){M=D.getElementsByTag("*");x=0;for(r=M.count();x0;else{v=D.startPath();if(!B.isBlock&&i.editor.config.autoParagraph!==false&&(i.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&i.editor.editable().equals(v.blockLimit)&& -!v.block)&&(Q=i.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&i.editor.config.autoParagraph!==false?i.editor.activeEnterMode==CKEDITOR.ENTER_DIV?"div":"p":false)){Q=M.createElement(Q);Q.appendBogus();D.insertNode(Q);CKEDITOR.env.needsBrFiller&&(I=Q.getBogus())&&I.remove();D.moveToPosition(Q,CKEDITOR.POSITION_BEFORE_END)}if((v=D.startPath().block)&&!v.equals(E)){if(I=v.getBogus()){I.remove();w.push(v)}E=v}B.firstNotAllowed&&(o=1);if(o&&B.isElement){v=D.startContainer;for(K=null;v&&!m[v.getName()][B.name];){if(v.equals(u)){v= -null;break}K=v;v=v.getParent()}if(v){if(K){S=D.splitElement(K);i.zombies.push(S);i.zombies.push(K)}}else{K=u.getName();T=!x;v=x==y.length-1;K=d(B.node,K);for(var O=[],W=K.length,X=0,Z=void 0,$=0,aa=-1;X0;){d=a.getItem(b);if(!CKEDITOR.tools.trim(d.getHtml())){d.appendBogus();CKEDITOR.env.ie&&(CKEDITOR.env.version<9&&d.getChildCount())&&d.getFirst().remove()}}}return function(d){var e=d.startContainer,f=e.getAscendant("table",1),m=false;c(f.getElementsByTag("td"));c(f.getElementsByTag("th"));f=d.clone();f.setStart(e,0);f=a(f).lastBackward();if(!f){f=d.clone();f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);f=a(f).lastForward();m=true}f|| -(f=e);if(f.is("table")){d.setStartAt(f,CKEDITOR.POSITION_BEFORE_START);d.collapse(true);f.remove()}else{f.is({tbody:1,thead:1,tfoot:1})&&(f=b(f,"tr",m));f.is("tr")&&(f=b(f,f.getParent().is("thead")?"th":"td",m));(e=f.getBogus())&&e.remove();d.moveToPosition(f,m?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END)}}}()})(); -(function(){function a(){var a=this._.fakeSelection,b;if(a){b=this.getSelection(1);if(!b||!b.isHidden()){a.reset();a=0}}if(!a){a=b||this.getSelection(1);if(!a||a.getType()==CKEDITOR.SELECTION_NONE)return}this.fire("selectionCheck",a);b=this.elementPath();if(!b.compare(this._.selectionPreviousPath)){if(CKEDITOR.env.webkit)this._.previousActive=this.document.getActive();this._.selectionPreviousPath=b;this.fire("selectionChange",{selection:a,path:b})}}function e(){r=true;if(!s){b.call(this);s=CKEDITOR.tools.setTimeout(b, -200,this)}}function b(){s=null;if(r){CKEDITOR.tools.setTimeout(a,0,this);r=false}}function c(a){function b(c,d){return!c||c.type==CKEDITOR.NODE_TEXT?false:a.clone()["moveToElementEdit"+(d?"End":"Start")](c)}if(!(a.root instanceof CKEDITOR.editable))return false;var c=a.startContainer,d=a.getPreviousNode(v,null,c),e=a.getNextNode(v,null,c);return b(d)||b(e,1)||!d&&!e&&!(c.type==CKEDITOR.NODE_ELEMENT&&c.isBlockBoundary()&&c.getBogus())?true:false}function d(a){return a.getCustomData("cke-fillingChar")} -function f(a,b){var c=a&&a.removeCustomData("cke-fillingChar");if(c){if(b!==false){var d,e=a.getDocument().getSelection().getNative(),f=e&&e.type!="None"&&e.getRangeAt(0);if(c.getLength()>1&&f&&f.intersectsNode(c.$)){d=[e.anchorOffset,e.focusOffset];f=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0]--;f&&d[1]--;var g;f=e;if(!f.isCollapsed){g=f.getRangeAt(0);g.setStart(f.anchorNode,f.anchorOffset);g.setEnd(f.focusNode,f.focusOffset);g=g.collapsed}g&&d.unshift(d.pop())}}c.setText(h(c.getText())); -if(d){c=e.getRangeAt(0);c.setStart(c.startContainer,d[0]);c.setEnd(c.startContainer,d[1]);e.removeAllRanges();e.addRange(c)}}}function h(a){return a.replace(/\u200B( )?/g,function(a){return a[1]?" ":""})}function n(a,b,c){var d=a.on("focus",function(a){a.cancel()},null,null,-100);if(CKEDITOR.env.ie)var e=a.getDocument().on("selectionchange",function(a){a.cancel()},null,null,-100);else{var f=new CKEDITOR.dom.range(a);f.moveToElementEditStart(a);var g=a.getDocument().$.createRange();g.setStart(f.startContainer.$, -f.startOffset);g.collapse(1);b.removeAllRanges();b.addRange(g)}c&&a.focus();d.removeListener();e&&e.removeListener()}function i(a){var b=CKEDITOR.dom.element.createFromHtml('
     
    ',a.document);a.fire("lockSnapshot");a.editable().append(b);var c=a.getSelection(1),d=a.createRange(),e=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(b,CKEDITOR.POSITION_AFTER_START); -d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([d]);e.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=b}function k(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),f=e[0];if(e.length==1&&f.collapsed)if((d=f[d<38?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getAttribute("contenteditable")=="false"){a.getSelection().fake(d);c.data.preventDefault();c.cancel()}}}} -function o(a){for(var b=0;b=d.getLength()?h.setStartAfter(d):h.setStartBefore(d)); -e&&e.type==CKEDITOR.NODE_TEXT&&(g?h.setEndAfter(e):h.setEndBefore(e));d=new CKEDITOR.dom.walker(h);d.evaluator=function(d){if(d.type==CKEDITOR.NODE_ELEMENT&&d.isReadOnly()){var e=c.clone();c.setEndBefore(d);c.collapsed&&a.splice(b--,1);if(!(d.getPosition(h.endContainer)&CKEDITOR.POSITION_CONTAINS)){e.setStartAfter(d);e.collapsed||a.splice(b+1,0,e)}return true}return false};d.next()}}return a}var s,r,v=CKEDITOR.dom.walker.invisible(1),g=function(){function a(b){return function(a){var c=a.editor.createRange(); +(function(){CKEDITOR.inline=function(a,c){if(!CKEDITOR.env.isCompatible)return null;a=CKEDITOR.dom.element.get(a);if(a.getEditor())throw'The editor instance "'+a.getEditor().name+'" is already attached to the provided element.';var b=new CKEDITOR.editor(c,a,CKEDITOR.ELEMENT_MODE_INLINE),f=a.is("textarea")?a:null;if(f){b.setData(f.getValue(),null,true);a=CKEDITOR.dom.element.createFromHtml('
    '+f.getValue()+"
    ",CKEDITOR.document); +a.insertAfter(f);f.hide();f.$.form&&b._attachToForm()}else b.setData(a.getHtml(),null,true);b.on("loaded",function(){b.fire("uiReady");b.editable(a);b.container=a;b.setData(b.getData(1));b.resetDirty();b.fire("contentDom");b.mode="wysiwyg";b.fire("mode");b.status="ready";b.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,b)},null,null,1E4);b.on("destroy",function(){if(f){b.container.clearCustomData();b.container.remove();f.show()}b.element.clearCustomData();delete b.element});return b}; +CKEDITOR.inlineAll=function(){var a,c,b;for(b in CKEDITOR.dtd.$editable)for(var f=CKEDITOR.document.getElementsByTag(b),d=0,e=f.count();d{voiceLabel}<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation">{bottomHtml}')); +b=CKEDITOR.dom.element.createFromHtml(f.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:[a.lang.editor,a.name].join(", "),topHtml:h?''+h+"":"",contentId:a.ui.spaceId("contents"),bottomHtml:i?''+i+"":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(n==CKEDITOR.ELEMENT_MODE_REPLACE){c.hide(); +b.insertAfter(c)}else c.append(b);a.container=b;h&&a.ui.space("top").unselectable();i&&a.ui.space("bottom").unselectable();c=a.config.width;n=a.config.height;c&&b.setStyle("width",CKEDITOR.tools.cssLength(c));n&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(n));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(b,c){return a(b,c,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(b,c,f){return a(b, +c,f,CKEDITOR.ELEMENT_MODE_APPENDTO)};CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b",v="",a=f+a.replace(e,function(){return v+f})+v}a=a.replace(/\n/g,"
    ");b||(a=a.replace(RegExp("
    (?=)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g," ");a=a.replace(/(>|\s) /g,function(a,b){return b+" "}).replace(/ (?=<)/g," ");x(this,"text",a)},insertElement:function(a, +b){b?this.insertElementIntoRange(a,b):this.insertElementIntoSelection(a)},insertElementIntoRange:function(a,b){var c=this.editor,d=c.config.enterMode,e=a.getName(),f=CKEDITOR.dtd.$block[e];if(b.checkReadOnly())return false;b.deleteContents(1);b.startContainer.type==CKEDITOR.NODE_ELEMENT&&b.startContainer.is({tr:1,table:1,tbody:1,thead:1,tfoot:1})&&q(b);var g,h;if(f)for(;(g=b.getCommonAncestor(0,1))&&(h=CKEDITOR.dtd[g.getName()])&&(!h||!h[e]);)if(g.getName()in CKEDITOR.dtd.span)b.splitElement(g);else if(b.checkStartOfBlock()&& +b.checkEndOfBlock()){b.setStartBefore(g);b.collapse(true);g.remove()}else b.splitBlock(d==CKEDITOR.ENTER_DIV?"div":"p",c.editable());b.insertNode(a);return true},insertElementIntoSelection:function(a){g(this);var b=this.editor,c=b.activeEnterMode,b=b.getSelection(),d=b.getRanges()[0],e=a.getName(),e=CKEDITOR.dtd.$block[e];if(this.insertElementIntoRange(a,d)){d.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);if(e)if((e=a.getNext(function(a){return f(a)&&!i(a)}))&&e.type==CKEDITOR.NODE_ELEMENT&&e.is(CKEDITOR.dtd.$block))e.getDtd()["#"]? +d.moveToElementEditStart(e):d.moveToElementEditEnd(a);else if(!e&&c!=CKEDITOR.ENTER_BR){e=d.fixBlock(true,c==CKEDITOR.ENTER_DIV?"div":"p");d.moveToElementEditStart(e)}}b.selectRanges([d]);n(this)},setData:function(a,b){b||(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);if(this.status=="unloaded")this.status="ready";this.editor.fire("dataReady")},getData:function(a){var b=this.getHtml();a||(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable", +!a)},detach:function(){this.removeClass("cke_editable");this.status="detached";var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||a.config.ignoreEmptyParagraph!==false&&(b=b.replace(m,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data= +this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a, +"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE||a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus= +false},null,null,-1);this.on("focus",function(){this.hasFocus=true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus=true;a.once("contentDom",function(){a.focusManager.focus()})}this.isInline()&&this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var c=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var d=a.config.contentsLangDirection;this.getDirection(1)!= +d&&this.changeAttr("dir",d);var k=CKEDITOR.getCss();if(k){d=c.getHead();if(!d.getCustomData("stylesheet")){k=c.appendStyleText(k);k=new CKEDITOR.dom.element(k.ownerNode||k.owningElement);d.setCustomData("stylesheet",k);k.data("cke-temp",1)}}d=c.getCustomData("stylesheet_ref")||0;c.setCustomData("stylesheet_ref",d+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a"); +b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()});var l={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.domEvent.getKey(),d;if(c in l){var b=a.getSelection(),f,k=b.getRanges()[0],g=k.startPath(),h,i,m,c=c==8;if(CKEDITOR.env.ie&&CKEDITOR.env.version<11&&(f=b.getSelectedElement())||(f=e(b))){a.fire("saveSnapshot");k.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START);f.remove();k.select();a.fire("saveSnapshot");d=1}else if(k.collapsed)if((h=g.block)&&(m= +h[c?"getPrevious":"getNext"](p))&&m.type==CKEDITOR.NODE_ELEMENT&&m.is("table")&&k[c?"checkStartOfBlock":"checkEndOfBlock"]()){a.fire("saveSnapshot");k[c?"checkEndOfBlock":"checkStartOfBlock"]()&&h.remove();k["moveToElementEdit"+(c?"End":"Start")](m);k.select();a.fire("saveSnapshot");d=1}else if(g.blockLimit&&g.blockLimit.is("td")&&(i=g.blockLimit.getAscendant("table"))&&k.checkBoundaryOfElement(i,c?CKEDITOR.START:CKEDITOR.END)&&(m=i[c?"getPrevious":"getNext"](p))){a.fire("saveSnapshot");k["moveToElementEdit"+ +(c?"End":"Start")](m);k.checkStartOfBlock()&&k.checkEndOfBlock()?m.remove():k.select();a.fire("saveSnapshot");d=1}else if((i=g.contains(["td","th","caption"]))&&k.checkBoundaryOfElement(i,c?CKEDITOR.START:CKEDITOR.END))d=1}return!d});a.blockless&&(CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)&&this.attachListener(this,"keyup",function(b){if(b.data.getKeystroke()in l&&!this.getFirst(f)){this.appendBogus();b=a.createRange();b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START);b.select()}});this.attachListener(this, +"dblclick",function(b){if(a.readOnly)return false;b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie||this.attachListener(this,"mousedown",function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")&&!c.isReadOnly()){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button==2){b= +b.data.getTarget();if(!b.getOuterHtml().replace(m,"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()});this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()})}CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){b=b.data.domEvent.getKey();if(b in l){var c=b==8,d=a.getSelection().getRanges()[0], +b=d.startPath();if(d.collapsed){var e;a:{var f=b.block;if(f)if(d[c?"checkStartOfBlock":"checkEndOfBlock"]())if(!d.moveToClosestEditablePosition(f,!c)||!d.collapsed)e=false;else{if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var k=d.startContainer.getChild(d.startOffset-(c?1:0));if(k&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("hr")){a.fire("saveSnapshot");k.remove();e=true;break a}}if((d=d.startPath().block)&&(!d||!d.contains(f))){a.fire("saveSnapshot");var g;(g=(c?d:f).getBogus())&&g.remove();e=a.getSelection(); +g=e.createBookmarks();(c?f:d).moveChildren(c?d:f,false);b.lastElement.mergeSiblings();h(f,d,!c);e.selectBookmarks(g);e=true}}else e=false;else e=false}if(!e)return}else{c=d;e=b.block;g=c.endPath().block;if(!e||!g||e.equals(g))b=false;else{a.fire("saveSnapshot");(f=e.getBogus())&&f.remove();c.deleteContents();if(g.getParent()){g.moveChildren(e,false);b.lastElement.mergeSiblings();h(e,g,true)}c=a.getSelection().getRanges()[0];c.collapse(1);c.select();b=true}if(!b)return}a.getSelection().scrollIntoView(); +a.fire("saveSnapshot");return false}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c=a.getCustomData("stylesheet_ref");if(--c)a.setCustomData("stylesheet_ref",c);else{a.removeCustomData("stylesheet_ref");b.removeCustomData("stylesheet").remove()}}}this.editor.fire("contentDomUnload"); +delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var i=CKEDITOR.dom.walker.bogus(),m=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi,p=CKEDITOR.dom.walker.whitespaces(true),s=CKEDITOR.dom.walker.bookmark(false,true);CKEDITOR.on("instanceLoaded", +function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty();c.fire("lockSnapshot");a(b);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})});CKEDITOR.on("instanceCreated", +function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);var d=b.fire("ariaEditorHelpLabel",{}).label;if(d)if(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents")){var e=CKEDITOR.tools.getNextId(),d=CKEDITOR.dom.element.createFromHtml(''+d+"");c.append(d);a.changeAttr("aria-describedby",e)}}})}); +CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");var x=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,d){var e,f,j,k,l=[],r=d.range.startContainer;e=d.range.startPath();for(var r=g[r.getName()],h=0,i=c.getChildren(),m=i.count(),n=-1,q=-1,p=0,s=e.contains(g.$list);h-1)l[n].firstNotAllowed=1;if(q>-1)l[q].lastNotAllowed=1;return l}function c(b,d){var e=[],f=b.getChildren(),j=f.count(),k,l=0,r=g[d],h=!b.is(g.$inline)||b.is("br");for(h&&e.push(" ");l ",p.document);p.insertNode(t);p.setStartAfter(t)}E=new CKEDITOR.dom.elementPath(p.startContainer); +m.endPath=y=new CKEDITOR.dom.elementPath(p.endContainer);if(!p.collapsed){var z=y.block||y.blockLimit,I=p.getCommonAncestor();z&&(!z.equals(I)&&!z.contains(I)&&p.checkEndOfBlock())&&m.zombies.push(z);p.deleteContents()}for(;(C=a(p.startContainer)&&p.startContainer.getChild(p.startOffset-1))&&a(C)&&C.isBlockBoundary()&&E.contains(C);)p.moveToPosition(C,CKEDITOR.POSITION_BEFORE_END);e(p,m.blockLimit,E,y);if(t){p.setEndBefore(t);p.collapse();t.remove()}t=p.startPath();if(z=t.contains(d,false,1)){p.splitElement(z); +m.inlineStylesRoot=z;m.inlineStylesPeak=t.lastElement}t=p.createBookmark();(z=t.startNode.getPrevious(f))&&a(z)&&d(z)&&w.push(z);(z=t.startNode.getNext(f))&&a(z)&&d(z)&&w.push(z);for(z=t.startNode;(z=z.getParent())&&d(z);)w.push(z);p.moveToBookmark(t);if(t=q){t=m.range;if(m.type=="text"&&m.inlineStylesRoot){C=m.inlineStylesPeak;p=C.getDocument().createText("{cke-peak}");for(w=m.inlineStylesRoot.getParent();!C.equals(w);){p=p.appendTo(C.clone());C=C.getParent()}q=p.getOuterHtml().split("{cke-peak}").join(q)}C= +m.blockLimit.getName();if(/^\s+|\s+$/.test(q)&&"span"in CKEDITOR.dtd[C])var x=' ',q=x+q+x;q=m.editor.dataProcessor.toHtml(q,{context:null,fixForBody:false,dontFilter:m.dontFilter,filter:m.editor.activeFilter,enterMode:m.editor.activeEnterMode});C=t.document.createElement("body");C.setHtml(q);if(x){C.getFirst().remove();C.getLast().remove()}if((x=t.startPath().block)&&!(x.getChildCount()==1&&x.getBogus()))a:{var G;if(C.getChildCount()==1&&a(G=C.getFirst())&&G.is(r)){x= +G.getElementsByTag("*");t=0;for(w=x.count();t0;else{B=G.startPath();if(!y.isBlock&&m.editor.config.autoParagraph!==false&&(m.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&m.editor.editable().equals(B.blockLimit)&&!B.block)&&(Q=m.editor.activeEnterMode!= +CKEDITOR.ENTER_BR&&m.editor.config.autoParagraph!==false?m.editor.activeEnterMode==CKEDITOR.ENTER_DIV?"div":"p":false)){Q=x.createElement(Q);Q.appendBogus();G.insertNode(Q);CKEDITOR.env.needsBrFiller&&(K=Q.getBogus())&&K.remove();G.moveToPosition(Q,CKEDITOR.POSITION_BEFORE_END)}if((B=G.startPath().block)&&!B.equals(H)){if(K=B.getBogus()){K.remove();C.push(B)}H=B}y.firstNotAllowed&&(p=1);if(p&&y.isElement){B=G.startContainer;for(M=null;B&&!g[B.getName()][y.name];){if(B.equals(q)){B=null;break}M=B; +B=B.getParent()}if(B){if(M){T=G.splitElement(M);m.zombies.push(T);m.zombies.push(M)}}else{M=q.getName();U=!t;B=t==E.length-1;M=c(y.node,M);for(var N=[],W=M.length,X=0,Z=void 0,$=0,aa=-1;X0;){d=a.getItem(b);if(!CKEDITOR.tools.trim(d.getHtml())){d.appendBogus();CKEDITOR.env.ie&&(CKEDITOR.env.version<9&&d.getChildCount())&&d.getFirst().remove()}}}return function(d){var e=d.startContainer,f=e.getAscendant("table",1),g=false;c(f.getElementsByTag("td"));c(f.getElementsByTag("th"));f=d.clone();f.setStart(e,0);f=a(f).lastBackward();if(!f){f=d.clone();f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);f=a(f).lastForward();g=true}f|| +(f=e);if(f.is("table")){d.setStartAt(f,CKEDITOR.POSITION_BEFORE_START);d.collapse(true);f.remove()}else{f.is({tbody:1,thead:1,tfoot:1})&&(f=b(f,"tr",g));f.is("tr")&&(f=b(f,f.getParent().is("thead")?"th":"td",g));(e=f.getBogus())&&e.remove();d.moveToPosition(f,g?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END)}}}()})(); +(function(){function a(){var a=this._.fakeSelection,b;if(a){b=this.getSelection(1);if(!b||!b.isHidden()){a.reset();a=0}}if(!a){a=b||this.getSelection(1);if(!a||a.getType()==CKEDITOR.SELECTION_NONE)return}this.fire("selectionCheck",a);b=this.elementPath();if(!b.compare(this._.selectionPreviousPath)){if(CKEDITOR.env.webkit)this._.previousActive=this.document.getActive();this._.selectionPreviousPath=b;this.fire("selectionChange",{selection:a,path:b})}}function c(){s=true;if(!p){b.call(this);p=CKEDITOR.tools.setTimeout(b, +200,this)}}function b(){p=null;if(s){CKEDITOR.tools.setTimeout(a,0,this);s=false}}function f(a){function b(c,d){return!c||c.type==CKEDITOR.NODE_TEXT?false:a.clone()["moveToElementEdit"+(d?"End":"Start")](c)}if(!(a.root instanceof CKEDITOR.editable))return false;var c=a.startContainer,d=a.getPreviousNode(x,null,c),e=a.getNextNode(x,null,c);return b(d)||b(e,1)||!d&&!e&&!(c.type==CKEDITOR.NODE_ELEMENT&&c.isBlockBoundary()&&c.getBogus())?true:false}function d(a){return a.getCustomData("cke-fillingChar")} +function e(a,b){var c=a&&a.removeCustomData("cke-fillingChar");if(c){if(b!==false){var d,e=a.getDocument().getSelection().getNative(),f=e&&e.type!="None"&&e.getRangeAt(0);if(c.getLength()>1&&f&&f.intersectsNode(c.$)){d=[e.anchorOffset,e.focusOffset];f=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0]--;f&&d[1]--;var h;f=e;if(!f.isCollapsed){h=f.getRangeAt(0);h.setStart(f.anchorNode,f.anchorOffset);h.setEnd(f.focusNode,f.focusOffset);h=h.collapsed}h&&d.unshift(d.pop())}}c.setText(g(c.getText())); +if(d){c=e.getRangeAt(0);c.setStart(c.startContainer,d[0]);c.setEnd(c.startContainer,d[1]);e.removeAllRanges();e.addRange(c)}}}function g(a){return a.replace(/\u200B( )?/g,function(a){return a[1]?" ":""})}function n(a,b,c){var d=a.on("focus",function(a){a.cancel()},null,null,-100);if(CKEDITOR.env.ie)var e=a.getDocument().on("selectionchange",function(a){a.cancel()},null,null,-100);else{var f=new CKEDITOR.dom.range(a);f.moveToElementEditStart(a);var g=a.getDocument().$.createRange();g.setStart(f.startContainer.$, +f.startOffset);g.collapse(1);b.removeAllRanges();b.addRange(g)}c&&a.focus();d.removeListener();e&&e.removeListener()}function h(a){var b=CKEDITOR.dom.element.createFromHtml('
     
    ',a.document);a.fire("lockSnapshot");a.editable().append(b);var c=a.getSelection(1),d=a.createRange(),e=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(b,CKEDITOR.POSITION_AFTER_START); +d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([d]);e.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=b}function i(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),f=e[0];if(e.length==1&&f.collapsed)if((d=f[d<38?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getAttribute("contenteditable")=="false"){a.getSelection().fake(d);c.data.preventDefault();c.cancel()}}}} +function m(a){for(var b=0;b=d.getLength()?h.setStartAfter(d):h.setStartBefore(d)); +e&&e.type==CKEDITOR.NODE_TEXT&&(g?h.setEndAfter(e):h.setEndBefore(e));d=new CKEDITOR.dom.walker(h);d.evaluator=function(d){if(d.type==CKEDITOR.NODE_ELEMENT&&d.isReadOnly()){var e=c.clone();c.setEndBefore(d);c.collapsed&&a.splice(b--,1);if(!(d.getPosition(h.endContainer)&CKEDITOR.POSITION_CONTAINS)){e.setStartAfter(d);e.collapsed||a.splice(b+1,0,e)}return true}return false};d.next()}}return a}var p,s,x=CKEDITOR.dom.walker.invisible(1),q=function(){function a(b){return function(a){var c=a.editor.createRange(); c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return false}}function b(a){return function(b){var c=b.editor,d=c.createRange(),e;if(!(e=d.moveToClosestEditablePosition(b.selected,a)))e=d.moveToClosestEditablePosition(b.selected,!a);e&&c.getSelection().selectRanges([d]);c.fire("saveSnapshot");b.selected.remove();if(!e){d.moveToElementEditablePosition(c.editable());c.getSelection().selectRanges([d])}c.fire("saveSnapshot");return false}}var c=a(),d=a(1);return{37:c, -38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(b){function c(){var a=d.getSelection();a&&a.removeAllRanges()}var d=b.editor;d.on("contentDom",function(){var b=d.document,c=CKEDITOR.document,g=d.editable(),j=b.getBody(),l=b.getDocumentElement(),h=g.isInline(),i,o;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();if(i!==0)if((a=d.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$){a=d.createRange();a.moveToElementEditStart(g);a.select()}}, -null,null,-2);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){i&&CKEDITOR.env.webkit&&(i=d._.previousActive&&d._.previousActive.equals(b.getActive()));d.unlockSelection(i);i=0},null,null,-1);g.attachListener(g,"mousedown",function(){i=0});if(CKEDITOR.env.ie||h){var n=function(){o=new CKEDITOR.dom.selection(d.getSelection());o.lock()};p?g.attachListener(g,"beforedeactivate",n,null,null,-1):g.attachListener(d,"selectionCheck",n,null,null,-1);g.attachListener(g,CKEDITOR.env.webkit? -"DOMFocusOut":"blur",function(){d.lockSelection(o);i=1},null,null,-1);g.attachListener(g,"mousedown",function(){i=0})}if(CKEDITOR.env.ie&&!h){var t;g.attachListener(g,"mousedown",function(a){if(a.data.$.button==2){a=d.document.getSelection();if(!a||a.getType()==CKEDITOR.SELECTION_NONE)t=d.window.getScrollPosition()}});g.attachListener(g,"mouseup",function(a){if(a.data.$.button==2&&t){d.document.$.documentElement.scrollLeft=t.x;d.document.$.documentElement.scrollTop=t.y}t=null});if(b.$.compatMode!= -"BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)l.on("mousedown",function(a){function b(a){a=a.data.$;if(e){var c=j.$.createTextRange();try{c.moveToPoint(a.x,a.y)}catch(d){}e.setEndPoint(g.compareEndPoints("StartToStart",c)<0?"EndToEnd":"StartToStart",c);e.select()}}function d(){l.removeListener("mousemove",b);c.removeListener("mouseup",d);l.removeListener("mouseup",d);e.select()}a=a.data;if(a.getTarget().is("html")&&a.$.y7&&CKEDITOR.env.version<11){l.on("mousedown",function(a){if(a.data.getTarget().is("html")){c.on("mouseup",x);l.on("mouseup",x)}});var x=function(){c.removeListener("mouseup",x);l.removeListener("mouseup",x);var a=CKEDITOR.document.$.selection,d=a.createRange();a.type!="None"&&d.parentElement().ownerDocument==b.$&&d.select()}}}}g.attachListener(g,"selectionchange", -a,d);g.attachListener(g,"keyup",e,d);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){d.forceNextSelectionCheck();d.selectionChange(1)});if(h&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var y;g.attachListener(g,"mousedown",function(){y=1});g.attachListener(b.getDocumentElement(),"mouseup",function(){y&&e.call(d);y=0})}else g.attachListener(CKEDITOR.env.ie?g:b.getDocumentElement(),"mouseup",e,d);CKEDITOR.env.webkit&&g.attachListener(b,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:f(g)}}, -null,null,-1);g.attachListener(g,"keydown",k(d),null,null,-1)});d.on("setData",function(){d.unlockSelection();CKEDITOR.env.webkit&&c()});d.on("contentDomUnload",function(){d.unlockSelection()});if(CKEDITOR.env.ie9Compat)d.on("beforeDestroy",c,null,null,9);d.on("dataReady",function(){delete d._.fakeSelection;delete d._.hiddenSelectionContainer;d.selectionChange(1)});d.on("loadSnapshot",function(){var a=d.editable().getLast(function(a){return a.type==CKEDITOR.NODE_ELEMENT});a&&a.hasAttribute("data-cke-hidden-sel")&& -a.remove()},null,null,100);d.on("key",function(a){if(d.mode=="wysiwyg"){var b=d.getSelection();if(b.isFake){var c=g[a.data.keyCode];if(c)return c({editor:d,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});CKEDITOR.on("instanceReady",function(a){var b=a.editor;if(CKEDITOR.env.webkit){b.on("selectionChange",function(){var a=b.editable(),c=d(a);c&&(c.getCustomData("ready")?f(a):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){f(b.editable())},null,null,-1);var c, -e,a=function(){var a=b.editable();if(a)if(a=d(a)){var f=b.document.$.defaultView.getSelection();f.type=="Caret"&&f.anchorNode==a.$&&(e=1);c=a.getText();a.setText(h(c))}},g=function(){var a=b.editable();if(a)if(a=d(a)){a.setText(c);if(e){b.document.$.defaultView.getSelection().setPosition(a.$,a.getLength());e=0}}};b.on("beforeUndoImage",a);b.on("afterUndoImage",g);b.on("beforeGetData",a,null,null,0);b.on("getData",g)}});CKEDITOR.editor.prototype.selectionChange=function(b){(b?a:e).call(this)};CKEDITOR.editor.prototype.getSelection= +38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(b){function d(){var a=f.getSelection();a&&a.removeAllRanges()}var f=b.editor;f.on("contentDom",function(){var b=f.document,d=CKEDITOR.document,g=f.editable(),k=b.getBody(),l=b.getDocumentElement(),h=g.isInline(),m,n;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();if(m!==0)if((a=f.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$){a=f.createRange();a.moveToElementEditStart(g);a.select()}}, +null,null,-2);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){m&&CKEDITOR.env.webkit&&(m=f._.previousActive&&f._.previousActive.equals(b.getActive()));f.unlockSelection(m);m=0},null,null,-1);g.attachListener(g,"mousedown",function(){m=0});if(CKEDITOR.env.ie||h){var q=function(){n=new CKEDITOR.dom.selection(f.getSelection());n.lock()};o?g.attachListener(g,"beforedeactivate",q,null,null,-1):g.attachListener(f,"selectionCheck",q,null,null,-1);g.attachListener(g,CKEDITOR.env.webkit? +"DOMFocusOut":"blur",function(){f.lockSelection(n);m=1},null,null,-1);g.attachListener(g,"mousedown",function(){m=0})}if(CKEDITOR.env.ie&&!h){var w;g.attachListener(g,"mousedown",function(a){if(a.data.$.button==2){a=f.document.getSelection();if(!a||a.getType()==CKEDITOR.SELECTION_NONE)w=f.window.getScrollPosition()}});g.attachListener(g,"mouseup",function(a){if(a.data.$.button==2&&w){f.document.$.documentElement.scrollLeft=w.x;f.document.$.documentElement.scrollTop=w.y}w=null});if(b.$.compatMode!= +"BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)l.on("mousedown",function(a){function b(a){a=a.data.$;if(e){var c=k.$.createTextRange();try{c.moveToPoint(a.x,a.y)}catch(d){}e.setEndPoint(g.compareEndPoints("StartToStart",c)<0?"EndToEnd":"StartToStart",c);e.select()}}function c(){l.removeListener("mousemove",b);d.removeListener("mouseup",c);l.removeListener("mouseup",c);e.select()}a=a.data;if(a.getTarget().is("html")&&a.$.y7&&CKEDITOR.env.version<11){l.on("mousedown",function(a){if(a.data.getTarget().is("html")){d.on("mouseup",z);l.on("mouseup",z)}});var z=function(){d.removeListener("mouseup",z);l.removeListener("mouseup",z);var a=CKEDITOR.document.$.selection,c=a.createRange();a.type!="None"&&c.parentElement().ownerDocument==b.$&&c.select()}}}}g.attachListener(g,"selectionchange", +a,f);g.attachListener(g,"keyup",c,f);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){f.forceNextSelectionCheck();f.selectionChange(1)});if(h&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var t;g.attachListener(g,"mousedown",function(){t=1});g.attachListener(b.getDocumentElement(),"mouseup",function(){t&&c.call(f);t=0})}else g.attachListener(CKEDITOR.env.ie?g:b.getDocumentElement(),"mouseup",c,f);CKEDITOR.env.webkit&&g.attachListener(b,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:e(g)}}, +null,null,-1);g.attachListener(g,"keydown",i(f),null,null,-1)});f.on("setData",function(){f.unlockSelection();CKEDITOR.env.webkit&&d()});f.on("contentDomUnload",function(){f.unlockSelection()});if(CKEDITOR.env.ie9Compat)f.on("beforeDestroy",d,null,null,9);f.on("dataReady",function(){delete f._.fakeSelection;delete f._.hiddenSelectionContainer;f.selectionChange(1)});f.on("loadSnapshot",function(){var a=f.editable().getLast(function(a){return a.type==CKEDITOR.NODE_ELEMENT});a&&a.hasAttribute("data-cke-hidden-sel")&& +a.remove()},null,null,100);f.on("key",function(a){if(f.mode=="wysiwyg"){var b=f.getSelection();if(b.isFake){var c=q[a.data.keyCode];if(c)return c({editor:f,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});CKEDITOR.on("instanceReady",function(a){var b=a.editor;if(CKEDITOR.env.webkit){b.on("selectionChange",function(){var a=b.editable(),c=d(a);c&&(c.getCustomData("ready")?e(a):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){e(b.editable())},null,null,-1);var c, +f,a=function(){var a=b.editable();if(a)if(a=d(a)){var e=b.document.$.defaultView.getSelection();e.type=="Caret"&&e.anchorNode==a.$&&(f=1);c=a.getText();a.setText(g(c))}},h=function(){var a=b.editable();if(a)if(a=d(a)){a.setText(c);if(f){b.document.$.defaultView.getSelection().setPosition(a.$,a.getLength());f=0}}};b.on("beforeUndoImage",a);b.on("afterUndoImage",h);b.on("beforeGetData",a,null,null,0);b.on("getData",h)}});CKEDITOR.editor.prototype.selectionChange=function(b){(b?a:c).call(this)};CKEDITOR.editor.prototype.getSelection= function(a){if((this._.savedSelection||this._.fakeSelection)&&!a)return this._.savedSelection||this._.fakeSelection;return(a=this.editable())&&this.mode=="wysiwyg"?new CKEDITOR.dom.selection(a):null};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);if(a.getType()!=CKEDITOR.SELECTION_NONE){!a.isLocked&&a.lock();this._.savedSelection=a;return true}return false};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;if(b){b.unlock(a);delete this._.savedSelection; -return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var p= -typeof window.getSelection!="function",z=1;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection)var b=a,a=a.root;var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:z++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=a=c?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b){CKEDITOR.tools.extend(this._.cache,b._.cache);this.isFake=b.isFake;this.isLocked=b.isLocked;return this}b=p?this.document.$.selection:this.document.getWindow().$.getSelection(); -if(CKEDITOR.env.webkit)(b.type=="None"&&this.document.getActive().equals(a)||b.type=="Caret"&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b);else if(CKEDITOR.env.gecko)b&&(this.document.getActive().equals(a)&&b.anchorNode&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b,true);else if(CKEDITOR.env.ie){var d;try{d=this.document.getActive()}catch(e){}if(p)b.type=="None"&&(d&&d.equals(this.document.getDocumentElement()))&&n(a,null,true);else{(b=b&&b.anchorNode)&&(b=new CKEDITOR.dom.node(b)); +return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var o= +typeof window.getSelection!="function",u=1;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection)var b=a,a=a.root;var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:u++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=a=c?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b){CKEDITOR.tools.extend(this._.cache,b._.cache);this.isFake=b.isFake;this.isLocked=b.isLocked;return this}b=o?this.document.$.selection:this.document.getWindow().$.getSelection(); +if(CKEDITOR.env.webkit)(b.type=="None"&&this.document.getActive().equals(a)||b.type=="Caret"&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b);else if(CKEDITOR.env.gecko)b&&(this.document.getActive().equals(a)&&b.anchorNode&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b,true);else if(CKEDITOR.env.ie){var d;try{d=this.document.getActive()}catch(e){}if(o)b.type=="None"&&(d&&d.equals(this.document.getDocumentElement()))&&n(a,null,true);else{(b=b&&b.anchorNode)&&(b=new CKEDITOR.dom.node(b)); d&&(d.equals(this.document.getDocumentElement())&&b&&(a.equals(b)||a.contains(b)))&&n(a,null,true)}}d=this.getNative();var f,g;if(d)if(d.getRangeAt)f=(g=d.rangeCount&&d.getRangeAt(0))&&new CKEDITOR.dom.node(g.commonAncestorContainer);else{try{g=d.createRange()}catch(h){}f=g&&CKEDITOR.dom.element.get(g.item&&g.item(0)||g.parentElement())}if(!f||!(f.type==CKEDITOR.NODE_ELEMENT||f.type==CKEDITOR.NODE_TEXT)||!this.root.equals(f)&&!this.root.contains(f)){this._.cache.type=CKEDITOR.SELECTION_NONE;this._.cache.startElement= -null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var A={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype={getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=p?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:p?function(){var a= +null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var A={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype={getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=o?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:o?function(){var a= this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;if(d=="Text")b=CKEDITOR.SELECTION_TEXT;if(d=="Control")b=CKEDITOR.SELECTION_ELEMENT;if(c.createRange().parentElement())b=CKEDITOR.SELECTION_TEXT}catch(e){}return a.type=b}:function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(c.rangeCount==1){var c=c.getRangeAt(0),d=c.startContainer;if(d==c.endContainer&& -d.nodeType==1&&c.endOffset-c.startOffset==1&&A[d.childNodes[c.startOffset].nodeName.toLowerCase()])b=CKEDITOR.SELECTION_ELEMENT}return a.type=b},getRanges:function(){var a=p?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c);var d=b.parentElement();if(!d.hasChildNodes())return{container:d,offset:0};for(var e=d.children,f,g,h=b.duplicate(),i=0,l=e.length-1,t=-1,m,k;i<=l;){t=Math.floor((i+l)/2);f=e[t];h.moveToElementText(f);m=h.compareEndPoints("StartToStart", -b);if(m>0)l=t-1;else if(m<0)i=t+1;else return{container:d,offset:a(f)}}if(t==-1||t==e.length-1&&m<0){h.moveToElementText(d);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!h){f=e[e.length-1];return f.nodeType!=CKEDITOR.NODE_TEXT?{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}}for(d=e.length;h>0&&d>0;){g=e[--d];if(g.nodeType==CKEDITOR.NODE_TEXT){k=g;h=h-g.nodeValue.length}}return{container:k,offset:-h}}h.collapse(m>0?true:false);h.setEndPoint(m> -0?"StartToStart":"EndToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;if(!h)return{container:d,offset:a(f)+(m>0?0:1)};for(;h>0;)try{g=f[m>0?"previousSibling":"nextSibling"];if(g.nodeType==CKEDITOR.NODE_TEXT){h=h-g.nodeValue.length;k=g}f=g}catch(o){return{container:d,offset:a(f)}}return{container:k,offset:m>0?-h:k.nodeValue.length+h}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root); -d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d==CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e0)h=w-1;else if(i<0)j=w+1;else return{container:d,offset:a(f)}}if(w==-1||w==e.length-1&&i<0){k.moveToElementText(d);k.setEndPoint("StartToStart",b);k=k.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!k){f=e[e.length-1];return f.nodeType!=CKEDITOR.NODE_TEXT?{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}}for(d=e.length;k>0&&d>0;){g=e[--d];if(g.nodeType==CKEDITOR.NODE_TEXT){t=g;k=k-g.nodeValue.length}}return{container:t,offset:-k}}k.collapse(i>0?true:false);k.setEndPoint(i> +0?"StartToStart":"EndToStart",b);k=k.text.replace(/(\r\n|\r)/g,"\n").length;if(!k)return{container:d,offset:a(f)+(i>0?0:1)};for(;k>0;)try{g=f[i>0?"previousSibling":"nextSibling"];if(g.nodeType==CKEDITOR.NODE_TEXT){k=k-g.nodeValue.length;t=g}f=g}catch(m){return{container:d,offset:a(f)}}return{container:t,offset:i>0?-k:t.nodeValue.length+k}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root); +d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d==CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e1){h=a[a.length- -1];a[0].setEnd(h.endContainer,h.endOffset)}h=a[0];var a=h.collapsed,o,n,r;if((d=h.getEnclosedNode())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getName()in A&&(!d.is("a")||!d.getText()))try{r=d.$.createControlRange();r.addElement(d.$);r.select();return}catch(s){}(h.startContainer.type==CKEDITOR.NODE_ELEMENT&&h.startContainer.getName()in b||h.endContainer.type==CKEDITOR.NODE_ELEMENT&&h.endContainer.getName()in b)&&h.shrink(CKEDITOR.NODE_ELEMENT,true);r=h.createBookmark();b=r.startNode;if(!a)g=r.endNode;r=h.document.$.body.createTextRange(); -r.moveToElementText(b.$);r.moveStart("character",1);if(g){i=h.document.$.body.createTextRange();i.moveToElementText(g.$);r.setEndPoint("EndToEnd",i);r.moveEnd("character",-1)}else{o=b.getNext(k);n=b.hasAscendant("pre");o=!(o&&o.getText&&o.getText().match(i))&&(n||!b.hasPrevious()||b.getPrevious().is&&b.getPrevious().is("br"));n=h.document.createElement("span");n.setHtml("");n.insertBefore(b);o&&h.document.createText("").insertBefore(b)}h.setStartBefore(b);b.remove();if(a){if(o){r.moveStart("character", --1);r.select();h.document.$.selection.clear()}else r.select();h.moveToPosition(n,CKEDITOR.POSITION_BEFORE_START);n.remove()}else{h.setEndBefore(g);g.remove();r.select()}}else{g=this.getNative();if(!g)return;this.removeAllRanges();for(r=0;r=0){h.collapse(1);n.setEnd(h.endContainer.$,h.endOffset)}else throw t;}g.addRange(n)}}this.reset();this.root.fire("selectionchange")}}},fake:function(a){var b=this.root.editor;this.reset();i(b);var c=this._.cache,d=new CKEDITOR.dom.range(this.root);d.setStartBefore(a);d.setEndAfter(a);c.ranges=new CKEDITOR.dom.rangeList(d);c.selectedElement=c.startElement=a;c.type=CKEDITOR.SELECTION_ELEMENT; -c.selectedText=c.nativeSel=null;this.isFake=1;this.rev=z++;b._.fakeSelection=this;this.root.fire("selectionchange")},isHidden:function(){var a=this.getCommonAncestor();a&&a.type==CKEDITOR.NODE_TEXT&&(a=a.getParent());return!(!a||!a.data("cke-hidden-sel"))},createBookmarks:function(a){a=this.getRanges().createBookmarks(a);this.isFake&&(a.isFake=1);return a},createBookmarks2:function(a){a=this.getRanges().createBookmarks2(a);this.isFake&&(a.isFake=1);return a},selectBookmarks:function(a){for(var b= +!(c.type==CKEDITOR.NODE_ELEMENT&&A[c.getName()]&&(d=c)));e--)a.shrink(CKEDITOR.SHRINK_ELEMENT);return d&&d.$});return a.selectedElement=c?new CKEDITOR.dom.element(c):null},getSelectedText:function(){var a=this._.cache;if(a.selectedText!==void 0)return a.selectedText;var b=this.getNative(),b=o?b.type=="Control"?"":b.createRange().text:b.toString();return a.selectedText=b},lock:function(){this.getRanges();this.getStartElement();this.getSelectedElement();this.getSelectedText();this._.cache.nativeSel= +null;this.isLocked=1},unlock:function(a){if(this.isLocked){if(a)var b=this.getSelectedElement(),c=!b&&this.getRanges(),d=this.isFake;this.isLocked=0;this.reset();if(a)(a=b||c[0]&&c[0].getCommonAncestor())&&a.getAscendant("body",1)&&(d?this.fake(b):b?this.selectElement(b):this.selectRanges(c))}},reset:function(){this._.cache={};this.isFake=0;var a=this.root.editor;if(a&&a._.fakeSelection&&this.rev==a._.fakeSelection.rev){delete a._.fakeSelection;var b=a._.hiddenSelectionContainer;if(b){var c=a.checkDirty(); +a.fire("lockSnapshot");b.remove();a.fire("unlockSnapshot");!c&&a.resetDirty()}delete a._.hiddenSelectionContainer}this.rev=u++},selectElement:function(a){var b=new CKEDITOR.dom.range(this.root);b.setStartBefore(a);b.setEndAfter(a);this.selectRanges([b])},selectRanges:function(a){var b=this.root.editor,b=b&&b._.hiddenSelectionContainer;this.reset();if(b)for(var b=this.root,c,d=0;d1){h=a[a.length-1];a[0].setEnd(h.endContainer,h.endOffset)}h=a[0];var a=h.collapsed,n,q,p;if((c=h.getEnclosedNode())&&c.type==CKEDITOR.NODE_ELEMENT&&c.getName()in A&&(!c.is("a")||!c.getText()))try{p=c.$.createControlRange();p.addElement(c.$);p.select();return}catch(s){}if(h.startContainer.type==CKEDITOR.NODE_ELEMENT&&h.startContainer.getName()in b||h.endContainer.type==CKEDITOR.NODE_ELEMENT&&h.endContainer.getName()in b){h.shrink(CKEDITOR.NODE_ELEMENT,true);a=h.collapsed}p=h.createBookmark(); +b=p.startNode;if(!a)g=p.endNode;p=h.document.$.body.createTextRange();p.moveToElementText(b.$);p.moveStart("character",1);if(g){i=h.document.$.body.createTextRange();i.moveToElementText(g.$);p.setEndPoint("EndToEnd",i);p.moveEnd("character",-1)}else{n=b.getNext(m);q=b.hasAscendant("pre");n=!(n&&n.getText&&n.getText().match(i))&&(q||!b.hasPrevious()||b.getPrevious().is&&b.getPrevious().is("br"));q=h.document.createElement("span");q.setHtml("");q.insertBefore(b);n&&h.document.createText("").insertBefore(b)}h.setStartBefore(b); +b.remove();if(a){if(n){p.moveStart("character",-1);p.select();h.document.$.selection.clear()}else p.select();h.moveToPosition(q,CKEDITOR.POSITION_BEFORE_START);q.remove()}else{h.setEndBefore(g);g.remove();p.select()}}else{g=this.getNative();if(!g)return;this.removeAllRanges();for(p=0;p=0){h.collapse(1);q.setEnd(h.endContainer.$,h.endOffset)}else throw w;}g.addRange(q)}}this.reset();this.root.fire("selectionchange")}}},fake:function(a){var b=this.root.editor;this.reset();h(b);var c=this._.cache,d=new CKEDITOR.dom.range(this.root);d.setStartBefore(a);d.setEndAfter(a);c.ranges=new CKEDITOR.dom.rangeList(d);c.selectedElement=c.startElement=a;c.type=CKEDITOR.SELECTION_ELEMENT; +c.selectedText=c.nativeSel=null;this.isFake=1;this.rev=u++;b._.fakeSelection=this;this.root.fire("selectionchange")},isHidden:function(){var a=this.getCommonAncestor();a&&a.type==CKEDITOR.NODE_TEXT&&(a=a.getParent());return!(!a||!a.data("cke-hidden-sel"))},createBookmarks:function(a){a=this.getRanges().createBookmarks(a);this.isFake&&(a.isFake=1);return a},createBookmarks2:function(a){a=this.getRanges().createBookmarks2(a);this.isFake&&(a.isFake=1);return a},selectBookmarks:function(a){for(var b= [],c=0;c]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g, -" ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var g=a.getDocument().createElement("div");g.append(e);e.$.outerHTML="
    "+f+"
    ";e.copyAttributes(g.getFirst());e=g.getFirst().remove()}else e.setHtml(f);b=e}else f?b=s(c?[a.getHtml()]:k(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,h;if((h=c.getPrevious(C))&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("pre")){d=o(h.getHtml(),/\n$/,"")+"\n\n"+o(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="
    "+d+"
    ":c.setHtml(d);h.remove()}}else c&& -p(b)}function k(a){a.getName();var b=[];o(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+""+c+"
    "}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function o(a,b,c){var d="",e="",a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function s(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));
    -for(var d=0;d"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat(" ",a.length-1)+" "});if(c){var f=b.clone();f.setHtml(e);c.append(f)}else b.setHtml(e)}return c||b}function r(a,b){var c=this._.definition,
    -d=c.attributes,c=c.styles,e=j(this)[a.getName()],f=CKEDITOR.tools.isEmpty(d)&&CKEDITOR.tools.isEmpty(c),h;for(h in d)if(!((h=="class"||this._.definition.fullMatch)&&a.getAttribute(h)!=m(h,d[h]))&&!(b&&h.slice(0,5)=="data-")){f=a.hasAttribute(h);a.removeAttribute(h)}for(var i in c)if(!(this._.definition.fullMatch&&a.getStyle(i)!=m(i,c[i],true))){f=f||!!a.getStyle(i);a.removeStyle(i)}g(a,e,P[a.getName()]);f&&(this._.definition.alwaysRemoveElement?p(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==
    -CKEDITOR.ENTER_BR&&!a.hasAttributes()?p(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function v(a){for(var b=j(this),c=a.getElementsByTag(this.element),d,e=c.count();--e>=0;){d=c.getItem(e);d.isReadOnly()||r.call(this,d,true)}for(var f in b)if(f!=this.element){c=a.getElementsByTag(f);for(e=c.count()-1;e>=0;e--){d=c.getItem(e);d.isReadOnly()||g(d,b[f])}}}function g(a,b,c){if(b=b&&b.attributes)for(var d=0;d]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g,
    +" ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var g=a.getDocument().createElement("div");g.append(e);e.$.outerHTML="
    "+f+"
    ";e.copyAttributes(g.getFirst());e=g.getFirst().remove()}else e.setHtml(f);b=e}else f?b=p(c?[a.getHtml()]:i(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,h;if((h=c.getPrevious(D))&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("pre")){d=m(h.getHtml(),/\n$/,"")+"\n\n"+m(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="
    "+d+"
    ":c.setHtml(d);h.remove()}}else c&& +o(b)}function i(a){a.getName();var b=[];m(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+"
    "+c+"
    "}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function m(a,b,c){var d="",e="",a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function p(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));
    +for(var d=0;d"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat(" ",a.length-1)+" "});if(c){var f=b.clone();f.setHtml(e);c.append(f)}else b.setHtml(e)}return c||b}function s(a,b){var c=this._.definition,
    +d=c.attributes,c=c.styles,e=l(this)[a.getName()],f=CKEDITOR.tools.isEmpty(d)&&CKEDITOR.tools.isEmpty(c),g;for(g in d)if(!((g=="class"||this._.definition.fullMatch)&&a.getAttribute(g)!=j(g,d[g]))&&!(b&&g.slice(0,5)=="data-")){f=a.hasAttribute(g);a.removeAttribute(g)}for(var h in c)if(!(this._.definition.fullMatch&&a.getStyle(h)!=j(h,c[h],true))){f=f||!!a.getStyle(h);a.removeStyle(h)}q(a,e,I[a.getName()]);f&&(this._.definition.alwaysRemoveElement?o(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==
    +CKEDITOR.ENTER_BR&&!a.hasAttributes()?o(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function x(a){for(var b=l(this),c=a.getElementsByTag(this.element),d,e=c.count();--e>=0;){d=c.getItem(e);d.isReadOnly()||s.call(this,d,true)}for(var f in b)if(f!=this.element){c=a.getElementsByTag(f);for(e=c.count()-1;e>=0;e--){d=c.getItem(e);d.isReadOnly()||q(d,b[f])}}}function q(a,b,c){if(b=b&&b.attributes)for(var d=0;d",a||b.name,"");return c.join("")},getDefinition:function(){return this._.definition}};CKEDITOR.style.getStyleText=function(a){var b=a._ST;if(b)return b;var b=a.styles,c=a.attributes&&a.attributes.style||"",
    -d="";c.length&&(c=c.replace(L,";"));for(var e in b){var f=b[e],g=(e+":"+f).replace(L,";");f=="inherit"?d=d+g:c=c+g}c.length&&(c=CKEDITOR.tools.normalizeCssText(c,true));return a._ST=c+d};CKEDITOR.style.customHandlers={};CKEDITOR.style.addCustomHandler=function(a){var b=function(a){this._={definition:a};this.setup&&this.setup(a)};b.prototype=CKEDITOR.tools.extend(CKEDITOR.tools.prototypedCopy(CKEDITOR.style.prototype),{assignedTo:CKEDITOR.STYLE_OBJECT},a,true);return this.customHandlers[a.type]=b};
    -var J=CKEDITOR.POSITION_PRECEDING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED,F=CKEDITOR.POSITION_FOLLOWING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED})();CKEDITOR.styleCommand=function(a,e){this.requiredContent=this.allowedContent=this.style=a;CKEDITOR.tools.extend(this,e,true)};CKEDITOR.styleCommand.prototype.exec=function(a){a.focus();this.state==CKEDITOR.TRISTATE_OFF?a.applyStyle(this.style):this.state==CKEDITOR.TRISTATE_ON&&a.removeStyle(this.style)};
    -CKEDITOR.stylesSet=new CKEDITOR.resourceManager("","stylesSet");CKEDITOR.addStylesSet=CKEDITOR.tools.bind(CKEDITOR.stylesSet.add,CKEDITOR.stylesSet);CKEDITOR.loadStylesSet=function(a,e,b){CKEDITOR.stylesSet.addExternal(a,e,"");CKEDITOR.stylesSet.load(a,b)};
    -CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{attachStyleStateChange:function(a,e){var b=this._.styleStateChangeCallbacks;if(!b){b=this._.styleStateChangeCallbacks=[];this.on("selectionChange",function(a){for(var d=0;d"}});"use strict";
    -(function(){var a={},e={},b;for(b in CKEDITOR.dtd.$blockLimit)b in CKEDITOR.dtd.$list||(a[b]=1);for(b in CKEDITOR.dtd.$block)b in CKEDITOR.dtd.$blockLimit||b in CKEDITOR.dtd.$empty||(e[b]=1);CKEDITOR.dom.elementPath=function(b,d){var f=null,h=null,n=[],i=b,k,d=d||b.getDocument().getBody();do if(i.type==CKEDITOR.NODE_ELEMENT){n.push(i);if(!this.lastElement){this.lastElement=i;if(i.is(CKEDITOR.dtd.$object)||i.getAttribute("contenteditable")=="false")continue}if(i.equals(d))break;if(!h){k=i.getName();
    -i.getAttribute("contenteditable")=="true"?h=i:!f&&e[k]&&(f=i);if(a[k]){var o;if(o=!f){if(k=k=="div"){a:{k=i.getChildren();o=0;for(var s=k.count();o-1}:typeof a=="function"?c=a:typeof a=="object"&&(c=
    -function(b){return b.getName()in a});var d=this.elements,f=d.length;e&&f--;if(b){d=Array.prototype.slice.call(d,0);d.reverse()}for(e=0;e=c){f=d.createText("");f.insertAfter(this)}else{a=d.createText("");a.insertAfter(f);a.remove()}return f},substring:function(a,
    -e){return typeof e!="number"?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,e)}});
    -(function(){function a(a,c,d){var e=a.serializable,h=c[d?"endContainer":"startContainer"],n=d?"endOffset":"startOffset",i=e?c.document.getById(a.startNode):a.startNode,a=e?c.document.getById(a.endNode):a.endNode;if(h.equals(i.getPrevious())){c.startOffset=c.startOffset-h.getLength()-a.getPrevious().getLength();h=a.getNext()}else if(h.equals(a.getPrevious())){c.startOffset=c.startOffset-h.getLength();h=a.getNext()}h.equals(i.getParent())&&c[n]++;h.equals(a.getParent())&&c[n]++;c[d?"endContainer":"startContainer"]=
    -h;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,e)};var e={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),d=[],e;return{getNextRange:function(h){e=e==void 0?0:e+1;var n=a[e];if(n&&a.length>1){if(!e)for(var i=a.length-1;i>=0;i--)d.unshift(a[i].createBookmark(true));if(h)for(var k=0;a[e+k+1];){for(var o=n.document,h=0,i=o.getById(d[k].endNode),o=o.getById(d[k+
    -1].startNode);;){i=i.getNextSourceNode(false);if(o.equals(i))h=1;else if(c(i)||i.type==CKEDITOR.NODE_ELEMENT&&i.isBlockBoundary())continue;break}if(!h)break;k++}for(n.moveToBookmark(d.shift());k--;){i=a[++e];i.moveToBookmark(d.shift());n.setEnd(i.endContainer,i.endOffset)}}return n}}},createBookmarks:function(b){for(var c=[],d,e=0;e"}});"use strict";
    +(function(){var a={},c={},b;for(b in CKEDITOR.dtd.$blockLimit)b in CKEDITOR.dtd.$list||(a[b]=1);for(b in CKEDITOR.dtd.$block)b in CKEDITOR.dtd.$blockLimit||b in CKEDITOR.dtd.$empty||(c[b]=1);CKEDITOR.dom.elementPath=function(b,d){var e=null,g=null,n=[],h=b,i,d=d||b.getDocument().getBody();do if(h.type==CKEDITOR.NODE_ELEMENT){n.push(h);if(!this.lastElement){this.lastElement=h;if(h.is(CKEDITOR.dtd.$object)||h.getAttribute("contenteditable")=="false")continue}if(h.equals(d))break;if(!g){i=h.getName();
    +h.getAttribute("contenteditable")=="true"?g=h:!e&&c[i]&&(e=h);if(a[i]){var m;if(m=!e){if(i=i=="div"){a:{i=h.getChildren();m=0;for(var p=i.count();m-1}:typeof a=="function"?f=a:typeof a=="object"&&(f=
    +function(b){return b.getName()in a});var d=this.elements,e=d.length;c&&e--;if(b){d=Array.prototype.slice.call(d,0);d.reverse()}for(c=0;c=f){e=d.createText("");e.insertAfter(this)}else{a=d.createText("");a.insertAfter(e);a.remove()}return e},substring:function(a,
    +c){return typeof c!="number"?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,c)}});
    +(function(){function a(a,c,d){var e=a.serializable,g=c[d?"endContainer":"startContainer"],n=d?"endOffset":"startOffset",h=e?c.document.getById(a.startNode):a.startNode,a=e?c.document.getById(a.endNode):a.endNode;if(g.equals(h.getPrevious())){c.startOffset=c.startOffset-g.getLength()-a.getPrevious().getLength();g=a.getNext()}else if(g.equals(a.getPrevious())){c.startOffset=c.startOffset-g.getLength();g=a.getNext()}g.equals(h.getParent())&&c[n]++;g.equals(a.getParent())&&c[n]++;c[d?"endContainer":"startContainer"]=
    +g;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,c)};var c={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),d=[],e;return{getNextRange:function(g){e=e==void 0?0:e+1;var n=a[e];if(n&&a.length>1){if(!e)for(var h=a.length-1;h>=0;h--)d.unshift(a[h].createBookmark(true));if(g)for(var i=0;a[e+i+1];){for(var m=n.document,g=0,h=m.getById(d[i].endNode),m=m.getById(d[i+
    +1].startNode);;){h=h.getNextSourceNode(false);if(m.equals(h))g=1;else if(c(h)||h.type==CKEDITOR.NODE_ELEMENT&&h.isBlockBoundary())continue;break}if(!g)break;i++}for(n.moveToBookmark(d.shift());i--;){h=a[++e];h.moveToBookmark(d.shift());n.setEnd(h.endContainer,h.endOffset)}}return n}}},createBookmarks:function(b){for(var c=[],d,e=0;eb?-1:1}),e=0,f;e
    ',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var e=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!!(e&&e==b)}catch(c){CKEDITOR.env.hc=false}a.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc"; -CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending){delete CKEDITOR._.pending;for(e=0;eb?-1:1}),e=0,f;e
    ',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var c=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!!(c&&c==b)}catch(f){CKEDITOR.env.hc=false}a.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc"; +CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending){delete CKEDITOR._.pending;for(c=0;carguments.length)){var c=i.call(this,a);c.labelId=CKEDITOR.tools.getNextId()+"_label";this._.children=[];CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"div",null,{role:"presentation"},function(){var f=[],d=a.required?" cke_required":"";"horizontal"!= -a.labelLayout?f.push('",'
    ',e.call(this,b,a),"
    "):(d={type:"hbox",widths:a.widths,padding:0,children:[{type:"html",html:'