Compare commits

...
Author SHA1 Message Date
Francis Lachapelle 9f7c205da9 Update ChangeLog 2016-02-05 12:41:38 -05:00
Francis Lachapelle 89655cf192 Preparation for release 3.0.1 2016-02-05 12:41:19 -05:00
Francis Lachapelle 7b4f4cbc0d (css) Fix display of link and image attachments 2016-02-05 12:04:26 -05:00
Francis Lachapelle 03c6118224 (css) Improve display of md-menus in Calendar 2016-02-05 11:17:42 -05:00
Francis Lachapelle f2da7d67d7 (js) Fix DnD of all-day events in multicolumn view 2016-02-05 11:15:45 -05:00
Francis Lachapelle 13a6290d0d (js) Respect sort when searching in Webmail 2016-02-05 11:06:20 -05:00
Francis Lachapelle 7a401374f2 (css,js) Update Angular Material to v1.0.5 2016-02-05 11:04:28 -05:00
Francis Lachapelle 2cc37238c1 Update NEWS file 2016-02-05 11:03:43 -05:00
Francis Lachapelle e3fbde3c16 (js) Verify $topIndex of virtual-repeater
Fixes #3500
2016-02-05 10:55:05 -05:00
Ludovic Marcotte b74dd0c942 Updated NEWS file 2016-02-05 10:10:27 -05:00
Ludovic Marcotte dd19f380ad (fix) added local memory pool 2016-02-05 10:03:56 -05:00
Ludovic Marcotte d3f59ba621 (fix) add shared/public namespaces in the list or returned folders 2016-02-05 10:01:18 -05:00
InverseBot aa5d1f3842 (js/css) Update generated files 2016-02-05 01:37:18 -05:00
Francis Lachapelle c68ca9dbb7 Aria attributes 2016-02-04 20:59:34 -05:00
Francis Lachapelle 5ccd33c126 Adapt to latest md changes 2016-02-04 20:59:34 -05:00
InverseBot 82f0471fb1 (js/css) Update generated files 2016-02-04 15:26:04 -05:00
InverseBot 7e06894700 (js/css) Update generated files 2016-02-04 15:19:27 -05:00
Patrice Levesque 1d868ff73c Remove 'git stash' commands from 'prod' recipe: it caused odd side-effects on pristine local repositories 2016-02-04 15:03:41 -05:00
Francis Lachapelle a49bb9ab03 (js) Update CKEditor to version 4.5.7 2016-02-04 14:16:43 -05:00
Francis Lachapelle d4ab5dd21b Update translations 2016-02-04 14:01:31 -05:00
Francis Lachapelle 5624bf47ae Update NEWS file 2016-02-04 12:43:29 -05:00
Francis Lachapelle d4e01a83a0 Handle broken messages with no date
Fixes #3498
2016-02-04 11:36:26 -05:00
Ludovic Marcotte c12200c7b5 (fix) correctly encode filename of attachments over EAS (#3491) 2016-02-04 10:34:55 -05:00
Francis Lachapelle a8845d262f Fix frequency detection of recurrence rule 2016-02-04 09:20:11 -05:00
Francis Lachapelle 7437ca1ab8 (js) Fix support for %p in date formatting 2016-02-04 09:19:25 -05:00
Francis Lachapelle 66d4b9ec43 (js/css) Update generated files 2016-02-04 08:37:59 -05:00
Francis Lachapelle f321e1c949 (js) Add "Move To" option for selected messages
Fixes #3477
2016-02-03 16:48:04 -05:00
Ludovic Marcotte 0e2b1e41a7 (fix) IMIP accept/decline when there is only one MIME part 2016-02-03 15:53:12 -05:00
Francis Lachapelle 1eced8bcec (js) Set summary when creating event from DnD 2016-02-03 14:37:53 -05:00
Francis Lachapelle 7099432caf (html) Fix for flexbox with Chrome 48 2016-02-03 14:37:14 -05:00
Francis Lachapelle 0236d9ef46 (js) Remove empty error callbacks for promises 2016-02-03 14:32:29 -05:00
InverseBot c4fdba678e Merge branch 'master' of github.com:inverse-inc/sogo 2016-02-03 14:00:01 -05:00
Francis Lachapelle bd4f42b948 Fix comparisons with NSNotFound
Fixes #3472
2016-02-03 13:54:08 -05:00
InverseBot 2bbb88e014 (js/css) Update generated files 2016-02-03 13:34:41 -05:00
Ludovic Marcotte 3c53186db3 (js/css) Update generated files 2016-02-03 10:56:12 -05:00
Francis Lachapelle 1a7e5c248f (js) Improve scrolling behavior when deleting msg
Fixes #3489
2016-02-03 10:53:26 -05:00
Francis Lachapelle 47701e880a (html) Fix display of a message raw source
Fixes #3490
2016-02-03 09:50:37 -05:00
Ludovic Marcotte 5413ac8aa3 (fix) make sure an email is defined before trying to use it (#3488) 2016-02-03 09:00:08 -05:00
Francis Lachapelle cf91004ae6 (js) Improve handling of IMAP connection problem
Fixes #194
2016-02-02 15:06:00 -05:00
Francis Lachapelle 15dee723d4 (css) Update SASS for latest md 2016-02-02 12:12:18 -05:00
Francis Lachapelle 50791b9c3a (css) Improve padding in message editor 2016-02-02 12:11:20 -05:00
Francis Lachapelle fa9bd3995e (css) Decrease all-day events margin in day view 2016-02-02 12:09:55 -05:00
Francis Lachapelle 4fb71ffdd0 (css) Fix for flexbox with Chrome 48
Fixes #3487
See https://github.com/angular/material/issues/6841
2016-02-02 12:07:44 -05:00
Francis Lachapelle 6bfc44fb53 (js) Add support for %p in date formatting
Fixes #3480
2016-02-01 20:54:37 -05:00
Francis Lachapelle a95fd25e09 (css) Improve display of mail attachments 2016-02-01 16:07:21 -05:00
Francis Lachapelle ff1dc9c82d (html) Wrap invitation in a md-card 2016-02-01 16:06:34 -05:00
Francis Lachapelle b5a7447cba Update NEWS file 2016-02-01 14:02:31 -05:00
Francis Lachapelle 687521d60c (js) Improve saving of events/tasks 2016-02-01 14:01:43 -05:00
Francis Lachapelle b09e7163dd (fix) Show user's default email address
Fixes #3473
2016-02-01 10:41:22 -05:00
Francis Lachapelle 020f362aa1 Merge pull request #193 from emiliojsf/master
(Fix) Calendar view when Descending Order is selected
2016-01-29 14:53:37 -05:00
Francis Lachapelle c7283b8890 (doc) Update repository in installation guide 2016-01-29 11:15:08 -05:00
Emílio Fonseca 978e1cdaf0 (Fix) Calendar view gets blank when Descending Order is selected 2016-01-29 13:32:58 -02:00
Francis Lachapelle c1ee1378ff (css) Improve toasts with an icon 2016-01-29 10:06:04 -05:00
Francis Lachapelle ac8cf16885 (fix) Safeguard againts mailboxes with no quota
Fixes #3468
2016-01-29 10:03:56 -05:00
Ludovic Marcotte fe0221f630 (fix) disable test 2016-01-27 14:43:23 -05:00
Francis Lachapelle 6e4aa79038 Update ChangeLog 2016-01-27 13:20:07 -05:00
Francis Lachapelle 34215142bd Preparation for release 3.0.0 2016-01-27 13:19:12 -05:00
Francis Lachapelle 0eb1b00c6b Update translations 2016-01-27 13:16:16 -05:00
Francis Lachapelle 3339f53f19 Update NEWS file 2016-01-27 13:03:45 -05:00
Francis Lachapelle d913bce41a Update .gitignore 2016-01-27 13:03:14 -05:00
Francis Lachapelle b82c96a2dd (js) Update CKEditor to version 4.5.6 2016-01-27 13:02:32 -05:00
Francis Lachapelle 85e181edc4 (js/css) Update generated files 2016-01-27 11:33:16 -05:00
Francis Lachapelle 841e29b091 (html) Improve task editor 2016-01-27 11:27:33 -05:00
Francis Lachapelle 30a96f33e9 Cleanup French locale 2016-01-27 11:27:33 -05:00
Francis Lachapelle ce80cd5ec0 (doc) Update installation guide 2016-01-27 11:27:33 -05:00
Francis Lachapelle c9944f0265 (doc) Update developer's guide 2016-01-27 11:27:33 -05:00
Francis Lachapelle dc2d153e0c Update npm package.json 2016-01-27 11:27:32 -05:00
Francis Lachapelle c2322eedac (font) Update Fira typeface 2016-01-27 11:27:31 -05:00
Francis Lachapelle b0f37e792f (css) Cleanup 2016-01-27 11:27:30 -05:00
Francis Lachapelle 7b297ed483 (css) Lower the font weight of buttons in sidenav 2016-01-27 11:27:30 -05:00
Francis Lachapelle 5a927dfeb3 Improve formatting of SOGoDefaults.plist 2016-01-27 11:27:30 -05:00
Ludovic Marcotte 5e60a68634 NEWS file update for v3 final 2016-01-27 10:41:11 -05:00
Francis Lachapelle be441f5cd3 (css,js) Fix display of all-day events 2016-01-27 10:28:40 -05:00
extrafu b064512549 Update README.md 2016-01-27 09:14:31 -05:00
extrafu 46c952ee13 Update README.md 2016-01-27 09:14:00 -05:00
Francis Lachapelle c21a2c647a (js/css) Update generated files 2016-01-26 09:17:16 -05:00
Francis Lachapelle 5754278ffa (css) Add $quarter_height variable in SchedulerUI 2016-01-26 09:09:25 -05:00
Francis Lachapelle 867bc4bc96 (css) Fix events spanning whole days 2016-01-26 09:07:00 -05:00
Francis Lachapelle 8a177bea47 (js) Fix date parsing on Safari 2016-01-26 09:07:00 -05:00
Francis Lachapelle 53b2e19fee Merge pull request #188 from emiliojsf/master
Adding TAB to separate recipients in Mail Editor
2016-01-25 15:02:47 -05:00
Emílio Fonseca bf3a072e6b Adding TAB to separate recipients in Mail Editor 2016-01-25 17:54:11 -02:00
Ludovic Marcotte dd59ef6c1d Remove superfluous code. 2016-01-25 14:32:56 -05:00
Ludovic Marcotte 5ad4d317df (fix) delete messages from Drafts folder that aren't meant to be sent 2016-01-25 14:21:38 -05:00
Francis Lachapelle b265de91fd Add Croatian translation 2016-01-25 11:14:45 -05:00
Ludovic Marcotte ec3e0e87f9 (doc) missing SOGoMailKeepDraftsAfterSend documentation 2016-01-25 11:09:29 -05:00
Ludovic Marcotte 9a6cf0eb66 (fix) do not try to decode non-wbxml responses for debug output (#3444) 2016-01-25 10:26:31 -05:00
Ludovic Marcotte 42c2edce19 (feat) newly created folders using EAS are always sync'ed by default (#3454) 2016-01-25 10:21:46 -05:00
ethoms 99765aae53 fix (revised) for issue with CardDAV sync with the DAVDroid sync client.
This is a revised fix for the issue raiased in sogo bug tracker 3370 and 3373. It supercedes the fix in commit  2c723070c6 .

The fix was noted in NEWS with the comment "we now return all cards when we receive an empty addressbook-query REPORT". However it did not work for me and at least two others, as can be seen in the commit comments. In summary, only contacts with email addresses were synced. The suggested change from kwirk fixes the regular address book sync, but it completely breaks syncing of the read-only Group Directory (Corporate Directory). My suggested changes work in full (as far as I'm able to test).

I have done some fairly extensive testing of CardDAV sync (with DAVDroid only) and it seems to work 100% now. In addition to the obvious tests, I have tested with contacts that only have one field of data entered. The feilds I've tested (with all other fields empty) are as follows:

First name
Last name
Display name
email address
Work (telephone)
Home (telephone)
Fax (telephone)
Mobile (telephone)

Additionally, I tested syncing of a contact with only the Work Address fully populated. In the webmail, since the name fields are all missing, the "Organization" field of the Work Address takes the place of the name field in the 'Name' column. This does get synced to my phone and it also appears my Android contact list with 'Name' set to the 'Organization' field data. The address, organization and website fields being in tact also.

In addition, I tested a Group Directory (Corporate Directory) [SOGoUserSources->isAddressBook] sync. It seems contacts without email addresses do not sync. This seems to be the behaviour across the board with a "." search filter. This happens despite the filter in SOGoUserSources including ldap entries without a mail attribute. Nothing I can do to patch this in SOGoFolder+CardDAV.m, that would have to be fixed in the code that deals with the special "." search filter (I guess).

I think the contact search system needs some looking into, particularly the "." search filter behaviour. There is another bug related to contact search in the webmail address book view. I will make a bug report on that soon. It's a shame there isn't an "all" search filter, it would seem it would make various parts of SOGo easier to get the right behaviour.
2016-01-25 09:19:41 -05:00
Ludovic Marcotte 3c0059192d (fix) rewrote the string sanitization to be 32-bit Unicode safe 2016-01-22 20:31:55 -05:00
Ludovic Marcotte 81f13404bf (fix) more fix regarding recurrence exceptions and body tags
Conflicts:

	ActiveSync/iCalEvent+ActiveSync.m
2016-01-22 14:05:00 -05:00
Ludovic Marcotte 30377c0fd6 (feat) new junk/not junk capability with generic SMTP integration
Conflicts:

	NEWS
	SoObjects/Mailer/SOGoMailAccount.m
	SoObjects/SOGo/SOGoDefaults.plist
	SoObjects/SOGo/SOGoUser.m
	UI/MailerUI/UIxMailFolderActions.h
	UI/MailerUI/UIxMailFolderActions.m
	UI/MailerUI/product.plist
2016-01-22 13:58:45 -05:00
Ludovic Marcotte 88207d27c4 (fix) now always generate invitation updates when using EAS
Conflicts:

	NEWS
2016-01-22 13:50:48 -05:00
Ludovic Marcotte 9ba7425e56 (fix) make sure we escape control chars 2016-01-22 13:50:19 -05:00
Francis Lachapelle f1873edf21 (js) Display event ghost block start/end hours 2016-01-21 20:46:04 -05:00
Francis Lachapelle 54100c1f78 Bump version to 3.0.0b6 2016-01-21 15:00:56 -05:00
Francis Lachapelle 395400ca61 (js/css) Update generated files 2016-01-21 14:59:58 -05:00
Francis Lachapelle 218aeb5f28 (css) Fix display of md-cards in .view-detail 2016-01-21 14:58:10 -05:00
Francis Lachapelle 522dc5f85a (js/css) Update generated files 2016-01-21 10:21:45 -05:00
Francis Lachapelle 8076a1a7ef Update translations 2016-01-21 10:17:36 -05:00
Francis Lachapelle 164f7fd709 (html) Hide export/source options for remote ab 2016-01-20 14:02:49 -05:00
Francis Lachapelle 21f5dd747b (css) Cleanup md-content 2016-01-19 15:23:15 -05:00
Francis Lachapelle 914379884b (css) Fix md-backdrop 2016-01-19 15:22:35 -05:00
Francis Lachapelle a83080ae3a (js,html) Improve Administration module
Warp ACL editor with a md-card and improve display on small screens.
2016-01-19 15:21:06 -05:00
Francis Lachapelle f3b7bf4728 (js) Fix md-require-match usage
Fixes #163
2016-01-18 20:10:59 -05:00
Francis Lachapelle 9e8ecfac2c (js) Fix display of prompt dialog on xs screens 2016-01-18 16:42:05 -05:00
Francis Lachapelle 01fd6f4309 (css) Various adjustments 2016-01-18 16:40:55 -05:00
Francis Lachapelle 2537abf9e6 (js) Use Date.format instead of local functions 2016-01-18 16:32:26 -05:00
Francis Lachapelle b316d928fc (css) Hide optional elements from messages list
Those elements will no longer flash when reload a mailbox.
2016-01-18 16:30:52 -05:00
Francis Lachapelle 0b189e9ca7 (css) Cleanup 2016-01-18 16:19:12 -05:00
Francis Lachapelle 629db3c728 (css) New .sg-input-no-message class 2016-01-18 16:17:17 -05:00
Francis Lachapelle 612f2867a1 (css) Improve timepicker 2016-01-18 16:15:33 -05:00
Francis Lachapelle 5076365956 (css) New .sg-input-no-message class
For md-autocomplete
2016-01-18 16:14:58 -05:00
Francis Lachapelle a440defa61 (html) Improve display of event/task viewer/editor 2016-01-18 16:12:54 -05:00
Francis Lachapelle c6bc8245a2 (css) Apply .sg-app-content to body 2016-01-18 16:04:09 -05:00
Francis Lachapelle 2e73b66e53 (js) Fix second search with same attributes 2016-01-18 15:37:27 -05:00
Francis Lachapelle 5c4da276bf (js) Fix handling of contact categories 2016-01-18 14:46:23 -05:00
Francis Lachapelle 3787296fd6 (js) Scroll to previously selected message 2016-01-18 14:33:31 -05:00
Ludovic Marcotte 691ab3ff27 (fix) don't use false, but use NO 2016-01-18 14:06:21 -05:00
Francis Lachapelle 3fa0c83626 (js) Close sidenav when searching on small screen 2016-01-18 14:05:33 -05:00
Francis Lachapelle 20adc2d821 (css) Improve display of msg viewer/editor 2016-01-18 14:03:28 -05:00
Francis Lachapelle a44ebee39a Merge pull request #186 from emiliojsf/master
Fix path of Draft mailbox when reply and forward message
2016-01-18 13:28:06 -05:00
Emílio Fonseca b55ef0d05b Fix path of Draft mailbox when reply and forward message 2016-01-18 16:04:28 -02:00
Francis Lachapelle 9984204987 (css) Improve display on mobile devices 2016-01-18 12:16:17 -05:00
Ludovic Marcotte e73fc61a27 (fix) old case with gcc 2016-01-18 11:14:56 -05:00
Ludovic Marcotte 9a831a85c0 (fix) re-enabled old test 2016-01-18 11:08:35 -05:00
Ludovic Marcotte c42fa9489c (fix) added new tests 2016-01-18 10:56:50 -05:00
Ludovic Marcotte b3bc6bb7dc Applied changes coming from v2/PR#184. 2016-01-18 09:26:07 -05:00
Ludovic Marcotte 985db9465c (fix) avoid generating empty ConversationId which can break Android EAS 2016-01-17 06:29:13 -05:00
Ludovic Marcotte defd9ab84b (fix) don't generate dual Body tag for event exceptions 2016-01-16 08:08:37 -05:00
Francis Lachapelle 3f2c6efe7e Fix path of Draft mailbox when composing message
Fixes #3439
2016-01-15 15:10:16 -05:00
Francis Lachapelle be6bd47c15 (js) Improve URL routing in Mail module 2016-01-15 15:09:13 -05:00
Francis Lachapelle eda112c4bd (html) Don't toggle checkbox when clicking input
When selecting a mail filter's name, the checkbox was toggled.

Fixes #128
2016-01-15 11:22:17 -05:00
Francis Lachapelle d29cdf2467 (html) No mail threading for now 2016-01-15 11:21:44 -05:00
Francis Lachapelle 94b6213ba9 (js) Use $window instead of window when possible 2016-01-15 10:26:38 -05:00
Francis Lachapelle 37a419a7ae (js) Set ACLs of new Addressbook
Fixes #133
2016-01-15 10:25:13 -05:00
Francis Lachapelle 2ba3371ea2 (js) Go to personal addressbook after deletion 2016-01-15 10:24:26 -05:00
Francis Lachapelle c7f1d8bb5e (js) Fix addressbook reload after a dry search
Fixes #148
2016-01-15 10:07:59 -05:00
Francis Lachapelle 8593ee9fbb Improve handling of ATTACH property (URIs) 2016-01-15 09:25:28 -05:00
Francis Lachapelle fe7a12bad0 (js) Improve initialization of Calendar model
Fixes #156
2016-01-14 16:41:58 -05:00
Francis Lachapelle d90e1dc0ec (html) Review ACL editor for better FF support
Fixes #152
2016-01-14 16:36:13 -05:00
Francis Lachapelle 27bf8459c2 (js/css) Update generated files 2016-01-14 16:10:37 -05:00
Francis Lachapelle f5432525ec (js) Remove dependency on stateAccount 2016-01-14 16:06:55 -05:00
Francis Lachapelle 59cc13f41c (css) Fix overflow on sidenav of Calendar module
Fixes #158
2016-01-14 16:00:12 -05:00
Francis Lachapelle 3d4311194c (js) Fix handling of new contact categories
Fixes #175
2016-01-14 15:52:04 -05:00
Francis Lachapelle 667e6dff7c (js) Fix selection of card
The previous card was incorrectly highlighted when revisiting an
addressbook.
2016-01-14 15:06:01 -05:00
Ludovic Marcotte f3ea377bdb (fix) correctly remove the event/task 2016-01-14 14:49:59 -05:00
Francis Lachapelle 491f8b4f2a (js) Fix sg-avatar-image with md-virtual-repeat 2016-01-14 14:45:50 -05:00
Francis Lachapelle 936f6cb073 (css) Improve cards/lists display of contacts 2016-01-14 14:21:42 -05:00
Francis Lachapelle 78771d7abb (js) Fix highlight of selected message 2016-01-13 16:24:37 -05:00
Francis Lachapelle e470996dcf Update French translation 2016-01-13 13:32:58 -05:00
Ludovic Marcotte ab23d0f766 (fix) added translations + fixed typos 2016-01-13 12:48:20 -05:00
Francis Lachapelle 2c59495005 (js) Unselect message when selecting same mailbox 2016-01-13 12:34:15 -05:00
Ludovic Marcotte 279ee42d35 (fix) added missing string for v2 2016-01-13 11:40:17 -05:00
Ludovic Marcotte 565ad6900b Updated JS files for new build 2016-01-13 09:18:14 -05:00
Ludovic Marcotte 302187410c (js/css) Update generated files 2016-01-13 09:15:07 -05:00
Ludovic Marcotte de47df7c76 (fix) more safeguards when expunging mailboxes with no quota 2016-01-12 16:02:10 -05:00
Ludovic Marcotte d2f164dfb7 (fix) safeguard ourselves againts mailboxes with no quota 2016-01-12 15:57:43 -05:00
Francis Lachapelle 98e780dc6f (html) Group nav buttons in Calendar views 2016-01-12 13:44:14 -05:00
Francis Lachapelle dad9b71a5b (js) Preserve selected date when switching view 2016-01-12 13:38:54 -05:00
Francis Lachapelle 77baffb85c Localize datepicker and respect user's defaults 2016-01-12 11:57:38 -05:00
Francis Lachapelle 09b3b4e13f (css,html) Colorize tags of messages 2016-01-08 22:06:35 -05:00
Francis Lachapelle dc43146a4e (html) Change bg color of selected message or card 2016-01-08 21:30:52 -05:00
Francis Lachapelle 6ba9bd5295 Improve sidenav of Webmail
- fixed highlight issue when multiple accounts are configured;
- added divider between accounts;
- added tooltips to account buttons.
2016-01-08 16:34:13 -05:00
Francis Lachapelle 1bcdac328a (js) Improve message selection when deleting msgs 2016-01-08 16:31:06 -05:00
Francis Lachapelle 5f9c87cae3 (css) Fix virtual-repeater scrolling bug 2016-01-08 16:18:32 -05:00
Francis Lachapelle b6076c9e8e Fix deletion of an appointment with attendees 2016-01-08 13:41:04 -05:00
Ludovic Marcotte 321193472d (fix) when deleting an event using EAS, properly invoke the auto-scheduling code 2016-01-08 13:13:39 -05:00
Ludovic Marcotte 0b5e38e88c (fix) we now follow 301 redirects when fetching ICS calendars 2016-01-08 13:13:35 -05:00
Ludovic Marcotte 4935eb23a9 (fix) Fixed DomainLessLogin logins in multi-domain 2016-01-08 11:40:02 -05:00
Ludovic Marcotte 96588aef6a (fix) properly handle mail subfolders for EAS 2016-01-08 10:29:14 -05:00
Francis Lachapelle f8d71966b9 Improve selection of mailboxes 2016-01-07 16:33:41 -05:00
Francis Lachapelle c86499f658 (js) Show indicator when swtiching mailbox 2016-01-07 16:32:08 -05:00
Francis Lachapelle 68c72f681c (js) Improve message viewer/editor in popup window 2016-01-07 15:45:16 -05:00
Francis Lachapelle 78dfe84d41 Localization of templates 2016-01-07 15:19:53 -05:00
Francis Lachapelle a1647d9cd0 Simplify creation of response in MailListActions 2016-01-07 15:12:13 -05:00
Ludovic Marcotte 030272d48a (fix) EAS folder name for emails in v3 2016-01-07 11:50:15 -05:00
Ludovic Marcotte ed799c5c35 (fix) EAS mail sync fix for v3 2016-01-07 11:39:15 -05:00
Ludovic Marcotte a755546f75 (fix) properly escape the UID too 2016-01-07 10:17:37 -05:00
Ludovic Marcotte 8237aaf74a (fix) updated NEWS file 2016-01-07 10:01:24 -05:00
Ludovic Marcotte 9d9f7b4c56 (fix) don't escape twice (#3423) 2016-01-07 10:01:17 -05:00
Ludovic Marcotte c355254467 (feat) UI + localization for previous commit 2016-01-07 09:24:29 -05:00
Ludovic Marcotte 6ffeba59ed (feat) Now able to sync only default mail folders when using EAS
Conflicts:

	NEWS
	SoObjects/SOGo/SOGoUserDefaults.h
	SoObjects/SOGo/SOGoUserDefaults.m
2016-01-07 09:19:44 -05:00
Francis Lachapelle 609c5aabf7 (html) Improve display of attachments 2016-01-06 12:30:34 -05:00
Francis Lachapelle 0bbdd01025 (html,css) Improve display of mail attachments
Added extension of file in a small frame
2016-01-06 12:24:34 -05:00
Francis Lachapelle c65a9155be Improve verification of SOGoLoginDomains array 2016-01-06 09:53:18 -05:00
Francis Lachapelle b19d894f27 Don't try to translate "SOGo" 2016-01-05 14:00:43 -05:00
Francis Lachapelle 23cc8154cc (js/css) Update generated files 2016-01-04 15:59:01 -05:00
Francis Lachapelle 2acf9830f1 (js) Move ngSanitize dependency to SOGo.Common 2016-01-04 11:51:59 -05:00
Francis Lachapelle 7121233a18 (css) Improve display of mail quota 2016-01-04 11:50:25 -05:00
Ludovic Marcotte ae6ed0c055 (feat) applied all changes as a patch coming from PR #180 2015-12-30 09:22:08 -05:00
Ludovic Marcotte d05fd407bd (fix) sync'ed NEWS file across branches 2015-12-30 09:09:51 -05:00
Francis Lachapelle 0d43b02cab (js) Fix the "save" method of Component service 2015-12-22 22:37:36 -05:00
Francis Lachapelle ccc0d61202 Improve display of preferences on small devices 2015-12-22 22:05:05 -05:00
Francis Lachapelle d18e80df08 (css) Visually identify attendees with no freebusy 2015-12-22 21:55:26 -05:00
Francis Lachapelle d08c490992 (js) Decompose contacts list in attendees editor 2015-12-22 17:24:16 -05:00
Francis Lachapelle 6464e35704 (js) Decompose contacts list in mail editor 2015-12-22 16:58:40 -05:00
Francis Lachapelle 711e159b4b Properly compute the last week number for the year
Fixes #1010
2015-12-22 14:19:02 -05:00
Francis Lachapelle ee4e46439f Indentation 2015-12-21 22:20:32 -05:00
Francis Lachapelle b8a7a43fd0 (js) Fix md-checkbox in md-menu 2015-12-21 22:19:10 -05:00
Francis Lachapelle 50f4f8a5c6 Localization 2015-12-21 22:18:20 -05:00
Francis Lachapelle 1419f4b6ca Improve JSON responses for message actions 2015-12-21 22:17:40 -05:00
Francis Lachapelle 1e45606681 (js) Remove obsolete call to "catch" 2015-12-21 21:19:19 -05:00
Francis Lachapelle 1c0cd2bc28 (html) Improve display of members of a list 2015-12-21 21:18:25 -05:00
Francis Lachapelle 662201a428 (js) Show message when emptying the trash 2015-12-21 21:16:20 -05:00
Francis Lachapelle 39510a3242 Show inbox quota information in Mail module 2015-12-21 21:14:39 -05:00
Francis Lachapelle 3cd1c641ab Improve JSON responses for mailbox actions 2015-12-21 21:07:43 -05:00
Francis Lachapelle d8f5673a14 (css) Force background-color of .md-button:hover 2015-12-21 12:03:23 -05:00
Francis Lachapelle c08f507dbf Fix multi-selection of cards in Firefox 2015-12-21 11:40:01 -05:00
Francis Lachapelle 40d9fa059f (html) Fix select menus of login page 2015-12-18 16:35:30 -05:00
Francis Lachapelle 33f03f1cfc Enable virtual-repeat in list of mailboxes 2015-12-18 16:33:01 -05:00
Francis Lachapelle 2628b0e79a "make prod" to build generated css/js files 2015-12-18 14:08:34 -05:00
Francis Lachapelle f778031176 Bump version to 3.0.0b5 2015-12-18 11:48:11 -05:00
Francis Lachapelle b7bfc17f5b (js/css) Update generated files 2015-12-18 11:46:19 -05:00
Francis Lachapelle 80533a6f9a (js) Automatically close sidenav on small devices 2015-12-18 11:43:05 -05:00
Francis Lachapelle 530e7a9d99 (css) Minor cleanup 2015-12-18 11:41:48 -05:00
Francis Lachapelle d2ecb1e95e (html) Fix toolbar alignment of messages list 2015-12-18 11:39:56 -05:00
Francis Lachapelle 39dfc84800 (css) Improve message editor 2015-12-18 11:38:53 -05:00
Francis Lachapelle 140803ebf2 (css) Improve autocompletion display 2015-12-18 09:11:36 -05:00
Francis Lachapelle d225865a3f (css) Don't wrap titles 2015-12-18 09:04:56 -05:00
Francis Lachapelle 904c2f071a (js) No 404 images with contacts chips 2015-12-17 23:34:25 -05:00
Francis Lachapelle f496f2b28f (js) Respect SOGoSearchMinimumSearchLength 2015-12-17 14:53:56 -05:00
Francis Lachapelle b1b8116aff (css) Fix for Safari 2015-12-17 14:26:02 -05:00
Francis Lachapelle e67661e84b (css) Improve buttons of sgToggleGrid directive 2015-12-17 14:25:05 -05:00
Francis Lachapelle 0f4e966ecf (html) Improve contact editor on small devices 2015-12-17 14:02:15 -05:00
Francis Lachapelle 89e947b07b (js) Improve handling of dates changes 2015-12-17 14:00:56 -05:00
Francis Lachapelle fe6bd70b72 (html,css) Group settings in event/task editors 2015-12-17 14:00:42 -05:00
Francis Lachapelle 020563016b (js) Component: move some code from $omit to $save 2015-12-17 13:22:57 -05:00
Francis Lachapelle 52863bf820 (js) Fix JavaScript syntax error in Component 2015-12-17 13:19:34 -05:00
Francis Lachapelle 4973580b4b (js) Improve parsing of dates in component editor 2015-12-16 22:16:04 -05:00
Francis Lachapelle 84199e9c70 (html) Improve color chip of events/tasks lists 2015-12-16 15:37:19 -05:00
Francis Lachapelle 8ff6fc8c1f (js) Fix DnD in multicolumn calendars view 2015-12-15 22:55:40 -05:00
Ludovic Marcotte 3404ae8105 (fix) carry over delegatedFrom too 2015-12-15 16:28:58 -05:00
Ludovic Marcotte 76f0ec9ae0 (fix) carry over the delegatedTo, not only the part stat 2015-12-15 16:28:13 -05:00
Ludovic Marcotte 0f578876af (fix) small fix over previous commit 2015-12-15 11:15:28 -05:00
Ludovic Marcotte cfb741fc9f (fix) avoid duplicating attendees when accepting event using a different identity over CalDAV
Conflicts:

	SoObjects/Appointments/iCalEntityObject+SOGo.h
2015-12-15 11:12:59 -05:00
Francis Lachapelle 544cb85e4d Localization 2015-12-15 10:05:46 -05:00
Francis Lachapelle ffe4d0f232 Localization 2015-12-15 09:56:52 -05:00
Francis Lachapelle 43926f28cb (js/css) Update generated files 2015-12-15 08:37:55 -05:00
Francis Lachapelle 5a8d986cad (js) Respect SOGoDefaultCalendar 2015-12-14 15:04:31 -05:00
Francis Lachapelle c7068b11a7 (css) Improve sg-color-chip of events/tasks lists 2015-12-14 13:53:39 -05:00
Francis Lachapelle 5499746b3a (js) Fix promise of $http in Resource service 2015-12-14 13:16:11 -05:00
Francis Lachapelle bb102e07e1 (js) Remove calls to deprecated methods 2015-12-14 12:20:39 -05:00
Francis Lachapelle 558795327e (js) Improve addressbook create/delete handling 2015-12-14 12:19:15 -05:00
Francis Lachapelle bbb9f31c29 (js) Remove calls to deprecated methods
$http.success and $http.error are deprecated.
2015-12-14 12:16:47 -05:00
Francis Lachapelle 5a1816a5d4 (js) Allow HTML in Dialog alert and confirm
Fixes #177
2015-12-14 12:09:58 -05:00
Francis Lachapelle 38fecebafd (js) Autofocus input field of prompt dialog 2015-12-14 11:30:27 -05:00
Francis Lachapelle 6dd5210cbf (css) Use md-secondary button in messages list
This fixes an issue with Firefox where it's impossible to select
multiple messages by clicking on the avatar.
2015-12-14 10:39:10 -05:00
Francis Lachapelle 04a6e579cf (js) Top navbar: remove divider when no logout
This happens when SOGoTrustProxyAuthentication is enabled.
2015-12-14 09:23:07 -05:00
Francis Lachapelle 5dfb57a955 (js/css) Update generated files 2015-12-11 15:44:55 -05:00
Francis Lachapelle 4b5abdb5ce Adapt to latest md changes
See https://github.com/angular/material/commit/17797f0
2015-12-11 15:12:43 -05:00
Ludovic Marcotte 0b10e20d37 (fix #127) fallback to "work" when export as ldif 2015-12-11 14:50:26 -05:00
Ludovic Marcotte 12da6dc021 (fix #119) disable servername and url by default 2015-12-11 14:10:39 -05:00
Ludovic Marcotte bf07ed5043 (fix #118) handle /.well-known/carddav 2015-12-11 14:02:59 -05:00
Ludovic Marcotte 153c2c33e5 (fix #147) can now add multiple mail labels 2015-12-10 15:12:12 -05:00
Ludovic Marcotte 96e93c0eeb (feat) show toast when compacting folder 2015-12-10 13:54:24 -05:00
Ludovic Marcotte 28399b698a (feat) show toast when saving and sending messages 2015-12-10 13:41:07 -05:00
Ludovic Marcotte 2657612136 (fix) use message instead of error as key 2015-12-10 13:40:31 -05:00
Ludovic Marcotte 89ae2a3d8d (fix #154) show toast upon subscribtion - strings will be added in separate commit 2015-12-10 13:24:31 -05:00
Francis Lachapelle cfe6a30d64 (js) Show all mailboxes when copying messages 2015-12-10 12:07:01 -05:00
Francis Lachapelle b74b73bca4 (js) Focus to new calendar/contact category field
Fixes #172
2015-12-10 11:47:43 -05:00
Francis Lachapelle eb539c1354 (js) Change icon color of color picker 2015-12-10 11:46:05 -05:00
Francis Lachapelle 6672e03c22 (js) Fix batch delete of messages
Internal arrays/objects were not properly updated.
2015-12-10 11:22:43 -05:00
Ludovic Marcotte ccd031693e (fix) also update the task editor 2015-12-10 11:16:00 -05:00
Ludovic Marcotte 156463c084 (fix #157) we now honor which calendars we can write to 2015-12-10 11:11:22 -05:00
Francis Lachapelle 95ca7d0193 (js) Don't allow export from system addressbook 2015-12-09 20:28:07 -05:00
Francis Lachapelle 28163b859c (html) Allow clicking on folder name to search 2015-12-09 20:27:22 -05:00
Francis Lachapelle 49fb7b3487 (js) Remove draggable CSS class when not editable 2015-12-09 20:13:19 -05:00
Francis Lachapelle 20b3a98282 (css/js) Update generated files 2015-12-09 19:38:22 -05:00
Francis Lachapelle 6e988404d9 (js) Make sure some arrays are initialized 2015-12-09 19:28:26 -05:00
Ludovic Marcotte 93ced64456 (fix) make sure empty categories are possible 2015-12-09 19:23:20 -05:00
Francis Lachapelle fcf88444c9 (css/js) Update generated files 2015-12-09 18:55:04 -05:00
Francis Lachapelle ad550a61bc (js) Fix JavaScript syntax 2015-12-09 18:53:35 -05:00
Francis Lachapelle f4e80aa819 (js) Adapt md-toast following changes in md 2015-12-09 18:52:52 -05:00
Ludovic Marcotte 450e3f03e2 (fix) make sure we don't have empty addresses 2015-12-09 18:40:03 -05:00
Ludovic Marcotte 889570d672 (fix) disabled message view caching for now 2015-12-09 18:24:09 -05:00
Francis Lachapelle 845624f569 (html) Add tooltip to "Today" button 2015-12-09 17:51:28 -05:00
Francis Lachapelle 90299dbe91 (css/js) Update generated files 2015-12-09 17:43:12 -05:00
Francis Lachapelle 52fd2174b8 (html) Adapt templates to new breakpoints 2015-12-09 16:52:54 -05:00
Francis Lachapelle 9389957b5f (js) Add upload by DnD in message editor 2015-12-09 16:01:59 -05:00
Francis Lachapelle 5020e3658b (html) Fix login page for mobile devices 2015-12-09 15:15:22 -05:00
Francis Lachapelle 767bad7c94 Remove duplicate key in MainUI localization 2015-12-09 15:14:34 -05:00
Ludovic Marcotte 9926653faa (fix) improve default vacation preferences handling 2015-12-09 14:06:07 -05:00
Francis Lachapelle 903c4280f4 (js) Set default Calendar view to 'week' 2015-12-09 11:55:21 -05:00
Francis Lachapelle 8ad854e30d (html) Fix mail account template 2015-12-09 09:49:27 -05:00
Francis Lachapelle aa54210c44 (css/js) Update generated files 2015-12-09 09:48:09 -05:00
Francis Lachapelle 464a405cee (js) Update Angular Material 2015-12-09 09:46:24 -05:00
Francis Lachapelle 5e978940f9 Improve JSON responses from server 2015-12-09 09:44:33 -05:00
Francis Lachapelle 38e5588c67 (html) Fix mail account template 2015-12-09 09:42:10 -05:00
Francis Lachapelle db96b2830e (js) Intercept and show HTTP errors in md-toast 2015-12-08 16:45:24 -05:00
Francis Lachapelle 24520418f9 Bump version to 3.0.0b4 2015-12-08 14:38:53 -05:00
Francis Lachapelle 623f2720f5 (css) Improve menu display in sidenav 2015-12-08 14:29:00 -05:00
Francis Lachapelle ccdb968e22 Revert "Fix HTML content type when attaching files"
This reverts commit 970f5e5bfe.
2015-12-08 14:14:46 -05:00
Francis Lachapelle 8d115733c3 (css) Fix language select menu of login page 2015-12-08 14:11:36 -05:00
Francis Lachapelle 375cccacc2 (css/js) Update generated files 2015-12-08 12:23:54 -05:00
Francis Lachapelle bc1d9020cc (js) Remove shrinkable toolbar in month's view 2015-12-08 12:16:25 -05:00
Francis Lachapelle c3ce235972 (html) Move delegation button of mail accounts 2015-12-08 12:10:15 -05:00
Francis Lachapelle b10f3e1d62 (html) Improve display of message editor 2015-12-08 11:56:36 -05:00
Francis Lachapelle 7b1febe841 Fix display of S/MIME signature validation 2015-12-08 11:56:01 -05:00
Francis Lachapelle 5289079d67 (html) Remove "from" label in message viewer 2015-12-08 11:49:57 -05:00
Francis Lachapelle 76dd9b1b32 (js) Fix compose message from Contacts module 2015-12-08 10:35:26 -05:00
Francis Lachapelle a79205fb7c (css) Move base_styles/* to structure.scss 2015-12-07 20:54:26 -05:00
Francis Lachapelle 11d03569a4 (css) Fix display of message in popup window 2015-12-07 16:41:06 -05:00
Francis Lachapelle ee71829134 (js) Fix reply and compose message links 2015-12-07 14:07:48 -05:00
Francis Lachapelle 04e0286992 (html) Improve Preferences template 2015-12-07 11:08:39 -05:00
Francis Lachapelle ecf297208a (js) Fix handling of prevent invitations whitelist 2015-12-07 11:05:01 -05:00
Francis Lachapelle a4bc4fc652 (js/css) Update generated files 2015-12-04 15:38:30 -05:00
Francis Lachapelle e14414ed56 Various Web interface changes
- more contrasted primary palette;
- white toolbars for list and detail views;
- modules navigation always visible;
- wrapped detail views into a md-card;
- moved preferences button to the sidenav;
- moved the sidenav toggle button to the top toolbar;
- new "flip" visual effect when looking at the raw source of cards and
  messages.
2015-12-04 15:15:44 -05:00
Ludovic Marcotte efba979f3d (fix) added missing Locale files 2015-12-04 13:56:46 -05:00
Ludovic Marcotte 9b5d37ff3d (fix) added missing languages to the plist 2015-12-04 13:52:10 -05:00
Ludovic Marcotte e80c7da188 (fix) add the languages to the classes too 2015-12-04 13:49:11 -05:00
Ludovic Marcotte b4aa8b27d4 (fix) added Macedonian and Portuguese to the GNUmakefile files 2015-12-04 13:43:36 -05:00
Ludovic Marcotte 3cd973b365 (fix) added missing files for Macedonian and Portuguese translations 2015-12-04 13:03:11 -05:00
Ludovic Marcotte c427a62fb0 (fix) sync'ed translations with files from v2 2015-12-04 12:48:52 -05:00
Francis Lachapelle d19369a5aa (js/css) Update vendor files 2015-12-04 09:07:07 -05:00
Ludovic Marcotte f4679ec4ab (fix) added missing strings for upcoming translations 2015-12-03 11:34:59 -05:00
Ludovic Marcotte fee7adfc0c Merge branch 'feature/addMissingStrings2' 2015-12-03 11:17:33 -05:00
Ludovic Marcotte 9b0a2c5fae Merge branch 'master' into feature/addMissingStrings2
Conflicts:
	UI/AdministrationUI/English.lproj/Localizable.strings
	UI/PreferencesUI/English.lproj/Localizable.strings
	UI/Templates/PreferencesUI/UIxFilterEditor.wox
2015-12-03 11:16:17 -05:00
Francis Lachapelle 9e776be2ce Enable zoom of images in message viewer 2015-12-03 10:35:34 -05:00
Francis Lachapelle 4189406962 Localization 2015-12-03 10:30:21 -05:00
Francis Lachapelle 348ba65c6e (js) Restore dot search in Contacts module 2015-12-02 16:43:06 -05:00
Francis Lachapelle af5599015b (js) Add links to attachments in message editor 2015-12-02 16:32:39 -05:00
Francis Lachapelle 8f8885a8ea (css) Fix "view-detail" of Calendar module
Will now take all space when sidenav is hidden.
2015-12-02 14:53:06 -05:00
Francis Lachapelle 9c2d7b5316 (css) Improve day and week display of events 2015-12-02 14:07:29 -05:00
Francis Lachapelle 8635eab502 Improve display of message attachments
Images are now "zoomable" by clicking on them.
2015-12-02 14:03:10 -05:00
Francis Lachapelle b02de72e57 Add sidenav toggle button in Calendar module
Also show the current list filter on the right side of the toggle
button.
2015-12-02 13:55:08 -05:00
Francis Lachapelle b2c2fc5dcd Improve attachments of message editor
Needed to disable multiple files upload at the same time until this bug
is resolved:

https://github.com/nervgh/angular-file-upload/issues/525
2015-12-02 10:52:35 -05:00
Francis Lachapelle f0ec431f30 (js) Refresh draft folder when saving new message 2015-12-01 16:26:30 -05:00
Francis Lachapelle 38963916aa Improve uglify Grunt task 2015-12-01 16:12:30 -05:00
Francis Lachapelle 970f5e5bfe Fix HTML content type when attaching files 2015-12-01 16:12:30 -05:00
Francis Lachapelle 5bb6cea09e (js,css) Improve handling of attachments in editor 2015-12-01 16:12:30 -05:00
Ludovic Marcotte 0660db7bf6 (fix) reverted change for #3389 2015-12-01 11:41:18 -05:00
Francis Lachapelle 827bc00982 (js) Fix list refresh when saving draft at index 0 2015-12-01 11:19:18 -05:00
Ludovic Marcotte d51d5c85b0 (fix) prevent characters in calendar component UID causing issues during import process 2015-12-01 09:27:27 -05:00
Francis Lachapelle fdb36970d4 (css) Adapt to new breakpoints and layout-align 2015-11-30 14:13:15 -05:00
Ludovic Marcotte 77755770a8 (feat) Initial support for EAS calendar exceptions 2015-11-30 09:19:25 -05:00
Francis Lachapelle 3106fa44c8 (html) Fix "no selection" right-side of Contacts 2015-11-27 17:34:42 -05:00
Francis Lachapelle 33edaacb13 (js) Fix height of virtual repeater in Contacts 2015-11-27 17:30:49 -05:00
Francis Lachapelle d19593afc3 (js) Allow hiding the sidenav on large screens
Position of button is not final as it causes an issue with the tabs of
the Calendar module.
2015-11-27 16:40:23 -05:00
Ludovic Marcotte 403d6fa54f (feat) you can now select your alternate avatar 2015-11-27 16:04:12 -05:00
Ludovic Marcotte 39455a283a (fix) replace .md-actions with md-dialog-actions in JS files 2015-11-27 13:56:31 -05:00
Ludovic Marcotte 6d29e50f22 (fix) replace .md-actions with md-dialog-actions 2015-11-27 13:00:39 -05:00
Francis Lachapelle 656835ca19 (html) Improve custom dialog for recurrent events 2015-11-27 11:58:14 -05:00
Ludovic Marcotte 62255eaf33 (feat) role can be displayed and set 2015-11-27 11:27:38 -05:00
Ludovic Marcotte ba7670ad59 (fix) added missing semicolon 2015-11-27 11:22:50 -05:00
Francis Lachapelle adb3c3a0e6 (html) Hide tags chips if message has none 2015-11-27 11:20:07 -05:00
Ludovic Marcotte dbffc50f06 (feat) screenname (x-aim) is now shown and editable 2015-11-27 10:58:56 -05:00
Ludovic Marcotte 4d0361759a (fix) use toast to warn about too many sent mails 2015-11-26 16:30:21 -05:00
Francis Lachapelle fe3f1d6678 (html) Improve priority icon for messages 2015-11-26 15:02:21 -05:00
Francis Lachapelle 93fa67c492 (css) Improve list in sidenav 2015-11-26 13:48:29 -05:00
Francis Lachapelle 00b22a71db (js) Only allow dragging of editable events 2015-11-26 13:31:26 -05:00
Francis Lachapelle 2e2d8b6963 (js) Fix count of selected events/tasks 2015-11-26 12:11:39 -05:00
Francis Lachapelle bb88643568 (js) Improve condition in CalendarListController 2015-11-26 12:04:52 -05:00
Francis Lachapelle f161cbe56b (html) Improve dates display in event/task viewer 2015-11-26 12:03:35 -05:00
Francis Lachapelle 71b03b925d (html) Fix "selected" icon for tasks list 2015-11-26 12:03:03 -05:00
Ludovic Marcotte 0114f8dc6a (fix) wrong check for viewable property 2015-11-26 11:38:31 -05:00
Francis Lachapelle 877ce7e4e4 (css) Add variable for latest md changes 2015-11-26 10:16:14 -05:00
Francis Lachapelle 418d30193b Improve users search in Administration module 2015-11-25 16:16:32 -05:00
Ludovic Marcotte 4edb87978b (fix) correctly handle all-day event exceptions when the master event changes 2015-11-25 15:53:48 -05:00
Ludovic Marcotte faa5d83cf6 (fix) EAS fix on qp-encoded subjects (#3390) 2015-11-25 15:09:48 -05:00
Francis Lachapelle 5f54ee09f1 (js) Fix navigation when deleting a card 2015-11-25 11:23:13 -05:00
Francis Lachapelle 5c6e19a67c (html) Add placeholder to search input fields 2015-11-25 11:23:12 -05:00
Francis Lachapelle 77fa55c6a4 (js) Reload the page when changing language cont'd 2015-11-25 11:23:12 -05:00
Francis Lachapelle 6c1e443d01 (css) Reclaim some space in addressbook list 2015-11-25 11:23:11 -05:00
Francis Lachapelle 6918650f5b (js) Close sidenav on small devices 2015-11-25 11:23:11 -05:00
Ludovic Marcotte b9462ed86d (fix) adjust doc and packaging regarding oc cleanup script 2015-11-25 10:57:16 -05:00
Francis Lachapelle 3d75d6affa (css) Reclaim some space in the left sidenav 2015-11-25 10:06:48 -05:00
Francis Lachapelle 69ff0634b4 (html) Fix "no selection" right-side views 2015-11-25 09:37:59 -05:00
Francis Lachapelle 00c923df7a (css) Add variables for latest md changes 2015-11-25 08:51:04 -05:00
Francis Lachapelle b6930fde26 (js) Move 'Reply All' action to a distinct button 2015-11-24 16:30:36 -05:00
Francis Lachapelle 5a5b4cc829 (js) Various improvements and fixes 2015-11-24 15:05:32 -05:00
Francis Lachapelle 333d279246 (js) Ask to reload the page when changing language 2015-11-24 15:05:32 -05:00
Ludovic Marcotte 9a12fb24a6 (fix) EAS fix for wrong charset being used (#3392) 2015-11-23 18:09:58 -05:00
Ludovic Marcotte 888db0b1be Updated NEWS file from v2 branch 2015-11-23 18:09:30 -05:00
Francis Lachapelle 3ec8847590 (html) Move 'Delete' button with action buttons
In appointment and task viewers.
2015-11-23 16:34:02 -05:00
Francis Lachapelle 823da43776 (html) Remove 'all day' option from task editor
This option is not supported yet (as in v2).
2015-11-23 16:25:09 -05:00
Francis Lachapelle c2d539d3c8 (js) Scroll to user's defined day start hour 2015-11-23 16:22:32 -05:00
Francis Lachapelle edf69e5989 (js) Improve task viewer 2015-11-23 15:12:44 -05:00
Ludovic Marcotte 6cdf9f55b4 (fix) fixed one unit test 2015-11-23 13:22:11 -05:00
Francis Lachapelle 3baef22df4 (js) Create event by dragging in month's view 2015-11-20 15:36:24 -05:00
Francis Lachapelle 90e79e7605 (js) Add drag'n'drop to month's view 2015-11-20 13:31:04 -05:00
Ludovic Marcotte 6a3a249bd6 (fix) domain in doc 2015-11-20 11:22:14 -05:00
extrafu c5017fc69e (fix) allow getting password from context for OC 2015-11-19 17:58:25 -05:00
Francis Lachapelle 7c86a43e48 (html) Fix color picker height in Firefox
Solution was to wrap the grid content into a md-content.
2015-11-17 15:15:39 -05:00
Francis Lachapelle e4eef9b780 (js,css) Show categories in events/tasks lists 2015-11-17 12:22:06 -05:00
Francis Lachapelle 2d6bdd37da (css) Improve display of events with categories 2015-11-17 11:28:50 -05:00
Francis Lachapelle 450ec06bd8 (js) Add shadow to event ghost (DnD) 2015-11-17 11:27:30 -05:00
Francis Lachapelle 0bd29e250e (css) Don't impose a transition to all md-toolbars 2015-11-16 16:19:25 -05:00
Francis Lachapelle 4685c0ff2a (js) Show events categories as color bands 2015-11-14 14:29:21 -05:00
Francis Lachapelle ace9fa9e4e (html) Show event location in Calendar days views 2015-11-13 15:45:44 -05:00
Francis Lachapelle 58dfa2e43b Return categories (from quick table) as an array 2015-11-13 14:43:03 -05:00
Francis Lachapelle 9d0bc5570b (js) Highlight selected color in color picker 2015-11-13 14:19:11 -05:00
Francis Lachapelle 5ed142853a (html) Mark recurrent events in Calendar views 2015-11-13 10:52:38 -05:00
Francis Lachapelle fc1dfcff10 (js) Fix display and refresh of freebusy 2015-11-13 10:46:32 -05:00
Francis Lachapelle 4f6c099564 (js) Fix creation of a task 2015-11-12 16:04:31 -05:00
Francis Lachapelle d081f3c6eb Display description for alarm & repeat definitions
This is not the perfect solution has the localization may be unsuitable
in some languages.
2015-11-12 12:29:10 -05:00
Francis Lachapelle 98493fbcac (css) Fix scrollbars in Calendar views 2015-11-11 16:44:26 -05:00
Francis Lachapelle b30f20e1b3 (js) Fix creation of a Calendar 2015-11-11 16:42:46 -05:00
Francis Lachapelle 87fc7b39cc Handle ExpandedFolders settings from v2 2015-11-11 16:01:37 -05:00
Ludovic Marcotte c7ef7a1d2f (feat) added calendar import/export feature 2015-11-11 11:18:49 -05:00
Francis Lachapelle 7316d13176 Improve contacts import dialog box 2015-11-10 15:05:41 -05:00
Francis Lachapelle 6a9e3a6e96 (html) Contextual icons in fab button of Calendars 2015-11-09 14:57:26 -05:00
Francis Lachapelle 0f2d0e9069 (css) Improve input field for folder name 2015-11-09 14:49:27 -05:00
Francis Lachapelle cc5cc30c24 (js) Improve addressbook renaming
The AddressBook instance to list the cards is now the same as the one
from the list of addressbooks. Therefore, when renaming an addressbook,
the addressbook name in the list is automatically updated.
2015-11-09 14:46:30 -05:00
Ludovic Marcotte dc3e42fd62 (fix) additional fix for #3356 2015-11-09 14:31:28 -05:00
Francis Lachapelle d23f7f1cc2 (js) Improve event creation through DnD 2015-11-09 12:15:16 -05:00
Francis Lachapelle a576b95663 (js) Add sgCalendarDayBlockGhost directive 2015-11-06 16:07:52 -05:00
Francis Lachapelle 2e02380a93 Initial drag'n'drop support in Calendar module
TODO:
- drag'n'drop in month's view;
- drag to create an all-day event;
- drag'n'drop to a calendar in the sidenav;
- touchscreen gestures support.
2015-11-06 15:15:37 -05:00
Ludovic Marcotte fc16d6ad90 (fix) fixed addressbrook-only source entires having a c_uid set
Conflicts:

	NEWS
	SoObjects/Contacts/SOGoContactSourceFolder.m
2015-11-06 13:38:26 -05:00
Ludovic Marcotte bdfe95a7fb (fix) fixed wrong comparison of meta vs. META tag in HTML mails
Conflicts:

	NEWS
2015-11-06 09:28:46 -05:00
Ludovic Marcotte 0d919dbd02 (fix) small fixes 2015-11-06 09:02:06 -05:00
Ludovic Marcotte c18f5c2246 We drop Lucid. 2015-11-05 15:40:40 -05:00
Ludovic Marcotte ab322093cd (fix) handle emails having an attachment as their content 2015-11-05 15:21:33 -05:00
Ludovic Marcotte 350fe2e7e0 (fix) one more fix for #3118 2015-11-05 14:59:41 -05:00
Francis Lachapelle f3f7ca8599 Merge pull request #115 from ethoms/clang-warnings-stage8
Stage 8 of clang compiler warning patches.
2015-11-05 13:22:08 -05:00
ethoms 3a103fd0dd Update WORequest+SOGo.m 2015-11-06 01:47:32 +08:00
Francis Lachapelle c5cac316c3 (js) Fix all-day dates covering a timezone change 2015-11-05 11:53:44 -05:00
Ludovic Marcotte a8525658b3 (fix) additional fix for #3118 2015-11-05 11:11:49 -05:00
Ludovic Marcotte 5f82d3fb37 (fix) manually added fixes from PR#120 2015-11-05 09:59:31 -05:00
Ludovic Marcotte edebdf1171 Updated NEWS file regarding PR#120
Conflicts:

	NEWS
2015-11-05 09:49:26 -05:00
extrafu 9fb69a8095 Merge pull request #113 from ethoms/clang-warnings-stage6
Stage 6 of clang compiler warning patches.
2015-11-05 09:34:24 -05:00
ethoms bf87c71671 Update WORequest+SOGo.m 2015-11-05 10:23:01 +08:00
ethoms 30488f4f6d Update WORequest+SOGo.m 2015-11-05 10:22:11 +08:00
ethoms 4ca3a1f42d Update SOGoParentFolder.m 2015-11-05 08:01:38 +08:00
ethoms efbf8825b1 Update SOGoParentFolder.m 2015-11-05 07:29:45 +08:00
ethoms becfdc215f Update SOGoParentFolder.h 2015-11-05 07:20:36 +08:00
extrafu d805cdfef4 Merge pull request #109 from ethoms/clang-warnings-stage2
Stage 2 of clang compiler warning patches.
2015-11-04 13:48:27 -05:00
extrafu cc1555af22 Merge pull request #108 from ethoms/clang-warnings-stage1
Stage1 of clang compiler warning patches.
2015-11-04 13:40:09 -05:00
Ludovic Marcotte 72b1fc1f3f (fix) wrong class for list avatar 2015-11-04 11:26:21 -05:00
Ludovic Marcotte 12863f1d2f (feat) now properties dialog for addressbooks
Now also possible to set the EAS sync flag for addressbooks
2015-11-04 10:38:24 -05:00
Ludovic Marcotte 39601a9cf9 (feat) moved FolderSynchronize code to GCS
Conflicts:

	SoObjects/Appointments/SOGoAppointmentFolder.h
2015-11-04 09:55:02 -05:00
ethoms 0cb1504f2c Update SOGoCalendarComponent.m 2015-11-04 08:18:23 +08:00
ethoms d19c883622 Update UIxCalListingActions.m 2015-11-04 07:38:47 +08:00
ethoms a1c6a6676e Update UIxCalListingActions.m 2015-11-04 07:37:34 +08:00
ethoms 59c8a0f722 Update UIxMailPartViewer.m 2015-11-04 07:33:28 +08:00
ethoms 6dc912eee5 Update SOGoGCSFolder.m 2015-11-04 07:29:50 +08:00
ethoms 1a0915ebf6 Update SOGoFolder.m 2015-11-04 07:27:53 +08:00
ethoms 6bff05b84a Update SOGoMailLabel.m 2015-11-04 07:24:02 +08:00
ethoms b5eb13c3b7 Update SOGoContactSourceFolder.m 2015-11-04 07:19:37 +08:00
extrafu 1fab2028e2 Merge pull request #116 from ethoms/clang-warnings-stage9
Stage 9 of clang compiler warning patches.
2015-11-03 18:08:28 -05:00
extrafu b7ebf6e781 Merge pull request #112 from ethoms/clang-warnings-stage5
Stage 5 of clang compiler warning patches.
2015-11-03 18:06:33 -05:00
extrafu d9a929762f Merge pull request #111 from ethoms/clang-warnings-stage4
Stage 4 of clang compiler warning patches.
2015-11-03 18:02:48 -05:00
extrafu 165337bebc Merge pull request #110 from ethoms/clang-warnings-stage3
Stage 3 of clang compiler warning patches.
2015-11-03 18:01:04 -05:00
Euan Thoms 46ccd04c70 Stage 9 of clang compiler warning patches. 2015-11-04 06:13:01 +08:00
Euan Thoms 440a86d3f2 Stage 8 of clang compiler warning patches. 2015-11-04 06:02:45 +08:00
Euan Thoms 0b4c355c2c Stage 6 of clang compiler warning patches. 2015-11-04 05:37:39 +08:00
Euan Thoms 36c1ba88a5 Stage 5 of clang compiler warning patches. 2015-11-04 05:28:03 +08:00
Euan Thoms 2303a250c5 Stage 4 of clang compiler warning patches. 2015-11-04 04:59:27 +08:00
Francis Lachapelle cbf9b6da3e Fix all-day events covering a timezone change 2015-11-03 15:47:30 -05:00
Euan Thoms a64d55f95c Stage 3 of clang compiler warning patches. 2015-11-04 03:59:40 +08:00
Euan Thoms 9a3f8549ea Delete .orig files left over from applying patches to Stage 2. 2015-11-04 03:40:53 +08:00
Euan Thoms 212ff64828 Stage 2 of clang compiler warning patches. 2015-11-04 03:36:34 +08:00
Euan Thoms 7541fb62e0 Merge branch 'master' of https://github.com/inverse-inc/sogo into clang-warnings-stage1 2015-11-04 02:14:50 +08:00
Francis Lachapelle e5c71a4179 Fix Brazilian (Portuguese) translation
Fixes #3355
2015-11-03 08:33:01 -05:00
Euan Thoms 3c62cc744e Stage 1 of clang compiler warning patches. 2015-10-31 14:10:03 +08:00
Francis Lachapelle 0b490a00c6 (js) Improve dates adjustments in editors
The main issue to resolve was to handle the time reset by the datepicker
when changing dates.
2015-10-30 08:38:19 -04:00
Francis Lachapelle 2bad5c0da4 (js) Preserve the time when changing the day 2015-10-29 16:17:37 -04:00
Francis Lachapelle 293b93ec69 (css) Color transition when changing calendar 2015-10-27 16:14:36 -04:00
Francis Lachapelle dc385a82ff (js) Reload page when session has expired 2015-10-27 11:31:31 -04:00
Francis Lachapelle 4172c216f5 (js) Select newly added user in ACLs editor 2015-10-27 11:28:42 -04:00
Ludovic Marcotte 09e354ed0e (feat) support for ghosted elements on contacts over EAS 2015-10-26 10:27:13 -04:00
Ludovic Marcotte d7fb79381e (feat) we now save the supported elements in EAS collections 2015-10-26 10:16:09 -04:00
Ludovic Marcotte 700a00b822 (fix) prevent postal address showing on single line over EAS (#2614) 2015-10-26 10:08:28 -04:00
extrafu ffbc20916a Merge pull request #105 from inverse-inc/feature/TestNGMailAddressParser
Added new tests for NGMailAddressParser
2015-10-23 15:52:11 -04:00
Ludovic Marcotte 746eea4bb6 (fix) perms fixes on scripts 2015-10-23 15:40:19 -04:00
Ludovic Marcotte 987700cd87 (fix) inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271) 2015-10-23 14:35:10 -04:00
Ludovic Marcotte a55d5c95b5 (fix) avoid crash when replying to a mail with no recipients (#3359) 2015-10-23 12:45:28 -04:00
Ludovic Marcotte 907c30061f (fix) we now return all cards when we receive an empty addressbook-query REPORT 2015-10-21 10:55:09 -04:00
Ludovic Marcotte 2ee09159a2 (feat) now possible to choose which folders to sync over EAS 2015-10-20 15:33:48 -04:00
Ludovic Marcotte 114622fee0 (fix) last fixes for cache coherency 2015-10-20 08:49:32 -04:00
Ludovic Marcotte 74085ed96b (fix) make sure a recipient is defined before enabling the Send button 2015-10-16 14:00:04 -04:00
Ludovic Marcotte 8979aebe31 (fix) oh well, old crappy distros 2015-10-15 16:11:30 -04:00
Ludovic Marcotte 020fa78848 (feat) initial S/MIME support for EAS (#3327) 2015-10-15 15:32:15 -04:00
Francis Lachapelle ddf97ebb13 (js) Improve sgColorPicker directive
- replaced sg-on-select by ng-model;
- set button background (no more wrapping necessary).
2015-10-15 15:13:28 -04:00
Ludovic Marcotte b02a4f82a0 (fix) don't escape quoted strings during versit generation 2015-10-15 12:51:58 -04:00
Francis Lachapelle 2c2c5241eb (css) Improve Calendar views 2015-10-15 10:50:24 -04:00
Francis Lachapelle 24ad2493a4 (css) Add material grey palette to Sass 2015-10-15 10:49:23 -04:00
Francis Lachapelle e0e4a94b87 (js) Improve handling of calendar color edition 2015-10-14 21:05:17 -04:00
Francis Lachapelle dc3741f436 (html,js) Adjust padding of dialogs
Following this commit:
https://github.com/angular/material/commit/d2140b1565b7e91d4f0b11b6c3983b976abd00f8
2015-10-14 10:31:15 -04:00
Francis Lachapelle feac8bb8f4 (js) Compute contrast color for background color 2015-10-14 10:00:14 -04:00
Francis Lachapelle 9f11e68de8 Group functions in utils.js 2015-10-14 10:00:13 -04:00
Ludovic Marcotte 15de70b938 (fix) make sure English is always used when generating Date headers using EAS (#3356) 2015-10-14 09:58:42 -04:00
Ludovic Marcotte ea24e9586b (fix) we no longer always rewrite emails for Outlook when using EAS 2015-10-14 09:30:35 -04:00
Ludovic Marcotte 4e9911419a (fix) handle references correctly over EAS (#3365) 2015-10-14 09:28:12 -04:00
Ludovic Marcotte bfa3cf379c (fix) numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
Conflicts:

	NEWS
2015-10-14 09:23:03 -04:00
Francis Lachapelle 4002da9323 (css) Adjustment in day/week views 2015-10-14 09:06:11 -04:00
Francis Lachapelle d5c09f6114 Change order of JS files in Mail module 2015-10-14 09:00:01 -04:00
Francis Lachapelle 737a19e653 (html) Add labels in appointment & task editors 2015-10-14 08:59:11 -04:00
Francis Lachapelle 43248fb5ea (html) Minor improvements to message viewer 2015-10-14 08:55:57 -04:00
Francis Lachapelle 1118dbceff Bump version to 3.0.0b3 2015-10-13 16:09:36 -04:00
Francis Lachapelle 369060a52f (js/css) Update generated files
With Angular Material 0.11.3
2015-10-13 16:05:39 -04:00
Francis Lachapelle 4da53b88b6 Fix display of Admin module on small devices 2015-10-13 14:30:09 -04:00
Francis Lachapelle 8924a2b670 (css) Adjustments in Contact viewer and editor 2015-10-09 16:50:54 -04:00
Francis Lachapelle 62537e0c15 (doc) Improve API documentation 2015-10-09 16:50:09 -04:00
Francis Lachapelle 7ef976bfe4 (css) Adjustments in Contact viewer and editor 2015-10-09 16:49:33 -04:00
Francis Lachapelle 05515673b7 Improve ACL editor 2015-10-09 16:49:08 -04:00
Francis Lachapelle 84f4698225 (js) Add missing dependency in Scheduler routing 2015-10-09 16:20:19 -04:00
Francis Lachapelle e5fdc28241 (js) Improve error handling in Contacts routing 2015-10-09 16:19:28 -04:00
Francis Lachapelle 7ddf7be705 Improve Administration module 2015-10-09 16:10:39 -04:00
Francis Lachapelle b63ee6998d (js) Fix avatar for groups 2015-10-09 16:03:05 -04:00
Francis Lachapelle 0b3095f7ed (js) Add missing dependency to Contact module 2015-10-09 16:01:56 -04:00
Francis Lachapelle e88af22708 (css) Minor improvement to preferences template 2015-10-09 13:37:01 -04:00
Francis Lachapelle d68008b5fd Update npm packages and add time-grunt 2015-10-09 12:11:37 -04:00
Francis Lachapelle e2d6e8ffe9 (js) Fix "file into" option of Sieve filter 2015-10-08 16:42:24 -04:00
Francis Lachapelle 2ad02b8386 (js) Improve User.$filter 2015-10-08 10:04:25 -04:00
Ludovic Marcotte 58afdedcc1 (fix) added missing template 2015-10-07 16:21:06 -04:00
Ludovic Marcotte e6be7e00ba (fix) improved the start/end delta handling and fixed the attendees viewer 2015-10-07 14:42:37 -04:00
Ludovic Marcotte 74eb0846e8 (fix) correctly round up the start/end/due time to 15 mins 2015-10-07 09:22:26 -04:00
Ludovic Marcotte 4f4b00cc54 (feat) first pass of working code for ACL admin module 2015-10-06 16:38:16 -04:00
Francis Lachapelle ad40bff91f Add missing content type to mixed part viewer 2015-10-06 15:38:37 -04:00
Francis Lachapelle 13f4e5a4de (css) Update SASS for latest md 2015-10-06 13:38:15 -04:00
Francis Lachapelle 459b183a42 Add jsdoc.json
Configuration file for jsdoc:

  jsdoc -c jsdoc.json
2015-10-06 09:55:32 -04:00
Francis Lachapelle 9fa9b7acab Improve mail (Sieve) filter editor
- Used 'controller as' syntax;
- Don't create new filter if user cancels the edition;
- Set default values for new filter.
2015-10-05 10:45:30 -04:00
Francis Lachapelle a393128d7a (js,html) View raw source of components 2015-10-02 17:01:06 -04:00
Francis Lachapelle 73ac55acfe (js) Fix advanced search construction 2015-10-02 15:43:02 -04:00
Francis Lachapelle e1393c3af2 Improve display of current day in month view 2015-10-02 15:13:30 -04:00
Francis Lachapelle d27c792324 (js,html) Allow the removable of attendees 2015-10-02 11:22:30 -04:00
Francis Lachapelle 42d306a24b (js) Remove some debugging output 2015-10-02 11:16:47 -04:00
Francis Lachapelle 6a7e06b0bb (html) Fix display of attendees
This bug is due to changes in the CSS of Angular Material.
2015-10-02 11:15:26 -04:00
Francis Lachapelle e36e9fd826 (html) Fix secondary click in calendars list 2015-10-02 09:41:57 -04:00
Francis Lachapelle aface8f8cf (html) Restore shadows in toolbars 2015-10-01 09:34:37 -04:00
Francis Lachapelle 790fc8e940 (js) Fix (sub)mailboxes state restore 2015-09-30 16:41:29 -04:00
Francis Lachapelle 35d1ca8775 Restore expandable mailboxes 2015-09-30 16:17:05 -04:00
Francis Lachapelle e8ca329070 Save mailboxes state as an array, not a string 2015-09-30 16:12:23 -04:00
Luc Charland f4ed7d9448 removed empty line 2015-09-30 12:17:20 -04:00
Luc Charland c7825601e6 removed author 2015-09-30 11:47:43 -04:00
Francis Lachapelle d07e254026 (js) Simplify Mailbox service 2015-09-30 11:29:05 -04:00
Francis Lachapelle 0f08571a83 (js) Fix ensureTarget filter to preserve CSS 2015-09-30 11:28:22 -04:00
Luc Charland 781e408091 - used tab instead of spaces for GNUMakefile
- removed unused logging
- used generic names
2015-09-30 11:23:45 -04:00
Luc Charland 549c57f8a6 Added new tests for NGMailAddressParser 2015-09-30 11:04:14 -04:00
Francis Lachapelle 45bc5a5368 Multicolumn day view
Restored multicolumn day view from v2.

Month view as also been improved.
2015-09-29 14:17:04 -04:00
Francis Lachapelle d51bef162a (js) Add border class for folder colors 2015-09-29 14:01:16 -04:00
Francis Lachapelle 1535660a31 Localization 2015-09-29 09:34:01 -04:00
Francis Lachapelle 90063ae563 (js) Fix error in advanced search mode 2015-09-29 08:58:23 -04:00
Francis Lachapelle b27cfc2bd8 (js) Delete an event or a task 2015-09-28 11:27:20 -04:00
Francis Lachapelle 5496dd97d9 Improve lists by compacting items 2015-09-25 16:07:56 -04:00
Francis Lachapelle 31f2e4b549 (js) Fix selected message class in advanced search
Also improved display of accounts subheaders.
2015-09-24 21:42:33 -04:00
Francis Lachapelle ec8c2d08e2 Fix handling of mail priority 2015-09-24 14:36:37 -04:00
Ludovic Marcotte 1ce9d71635 (fix) add robustness to part states handling 2015-09-24 13:21:23 -04:00
Francis Lachapelle a6c440d245 (js) Improve renaming an address book 2015-09-24 11:12:57 -04:00
Ludovic Marcotte e3d3eb171d (fix) small optimization 2015-09-24 11:03:19 -04:00
Francis Lachapelle 3a4365b15a (js) Fix ngSwitch in Contacts module 2015-09-24 11:01:27 -04:00
Francis Lachapelle 7f3d57c8dc (html) Sort by lastname and firstname in Contacts 2015-09-24 10:55:08 -04:00
Ludovic Marcotte e93853d910 (fix) make sure to open card's url in new window 2015-09-23 20:35:55 -04:00
Francis Lachapelle fcba6f2ddb Improve Calendar views
Moved toolbars inside "list" and "detail" views, like we do in the other
modules. With this change, it was possible to move the navitation button
(chevrons) inside the top toolbar of the detail view. Also move the view
headers inside a toolbar. The all-day events of the day and week views
have also been moved inside this toolbar.
2015-09-23 15:24:24 -04:00
Ludovic Marcotte eb50f94b2b (fix) remove ng-click to allow FF to select/unselect personal calendars 2015-09-23 09:22:01 -04:00
Francis Lachapelle 9f7b69b037 (html) Restore active module icon as disabled 2015-09-22 12:00:07 -04:00
Francis Lachapelle c6cd3b0e1d Update angular-material 2015-09-22 11:58:18 -04:00
Francis Lachapelle 31026a5bed (js/css) Update generated files 2015-09-22 11:56:33 -04:00
Francis Lachapelle 08fbc00d7a (js) Add "today" button to Calendar module 2015-09-22 10:17:17 -04:00
Francis Lachapelle 957336b24c (css) Fix display of attendees editor 2015-09-22 09:41:47 -04:00
Francis Lachapelle 2a925de52c (js, css) Improve timepicker 2015-09-21 21:13:28 -04:00
Francis Lachapelle cf8dd02411 (css) Wrap Scheduler directives in unique selector 2015-09-21 20:56:55 -04:00
Francis Lachapelle da37b3f868 Rename Slovenian template
Closes #104
2015-09-21 20:40:39 -04:00
Francis Lachapelle 24450a9c20 Update NEWS file
Conflicts:

	NEWS
2015-09-21 16:13:16 -04:00
Francis Lachapelle 964f79db35 Update NEWS file 2015-09-21 16:10:43 -04:00
Ludovic Marcotte c0f51cf731 (fix) don't offer the delete option on personal resources 2015-09-21 15:00:32 -04:00
Francis Lachapelle ae3ce7186d (css) Set flex width of search field menu 2015-09-21 12:29:07 -04:00
Francis Lachapelle 5034a72ed1 (css) Lower events overlapping (5% => 2%) 2015-09-21 12:29:06 -04:00
Ludovic Marcotte fdad1e061a (fix) set default's module path name to SOGo 2015-09-21 12:02:33 -04:00
Francis Lachapelle d5d54822b0 (html) Improve mail editor 2015-09-21 11:58:48 -04:00
Francis Lachapelle 81cdf5be7b (css) Update SASS for latest md 2015-09-21 11:50:47 -04:00
Francis Lachapelle 959ea0f62f (html) Show active tasks count in Calendar module 2015-09-21 09:49:14 -04:00
Ludovic Marcotte 1c666e8ead (fix) simplified logic and improved code readability 2015-09-21 08:59:36 -04:00
Ludovic Marcotte 7692832672 (fix) define -modulePath to avoid broken ApplicationURL path 2015-09-21 08:41:31 -04:00
Francis Lachapelle 2bfd9f983d (html) Hide timepicker when editing all-day event 2015-09-19 22:22:39 -04:00
Francis Lachapelle 2a5e19a762 (css) Improve Calendar views 2015-09-19 22:22:39 -04:00
Ludovic Marcotte 258f5e0b25 (fix) correctly handle module name for "folder less" SOGo modules 2015-09-18 20:30:35 -04:00
Ludovic Marcotte 3609e41205 (fix) make sure the autoreply addresses are always defined to avoid an exception in v2 if not 2015-09-18 19:55:37 -04:00
Francis Lachapelle 2ea2edbccd Display all-day events in day & week views
All-day events are now displayed and the day/week views are now built using flexbox.
2015-09-18 16:11:31 -04:00
Francis Lachapelle fab9e938fc Fix URL escaping unti test 2015-09-18 13:53:32 -04:00
Ludovic Marcotte 9a2f3914e2 (fix) additional fix for issue #3152 2015-09-17 16:29:21 -04:00
Ludovic Marcotte f13b3dd201 (fix) typo in previous commit 2015-09-17 16:02:45 -04:00
Ludovic Marcotte 413550a025 (fix) memory leak when sync'ing GCS collections 2015-09-17 15:59:20 -04:00
Francis Lachapelle 06ac255f1a (css) Badge for mail folders unread count 2015-09-17 15:08:21 -04:00
Francis Lachapelle 0682b24438 (fix) Selection of Mail advanced search criteria 2015-09-17 15:06:48 -04:00
Francis Lachapelle a01926b878 (html) Improve folder subscription dialog box 2015-09-17 10:37:35 -04:00
Francis Lachapelle 58ff8139e9 Allow rename of calendars 2015-09-17 10:33:12 -04:00
Francis Lachapelle 70bb512bb8 Improve folders rename 2015-09-16 16:15:04 -04:00
Francis Lachapelle 7b580f8af6 (js) Fix variable verification in sgSearch 2015-09-16 14:26:24 -04:00
Francis Lachapelle 11d9765a69 Add verbose output to "sogo-tool remove" command 2015-09-16 11:40:14 -04:00
Ludovic Marcotte 24a3ecd2d6 Updated .gitingore for EAS bundle 2015-09-16 10:41:20 -04:00
Ludovic Marcotte 139ed39663 (fix) update the doc regarding the temporarily dropping rhel/old debian/ubuntu support 2015-09-16 09:58:36 -04:00
Francis Lachapelle 875d23aea0 (js/css) Update generated files 2015-09-15 12:05:37 -04:00
Francis Lachapelle c4ad8a64fa (html) Improve login page 2015-09-15 11:42:47 -04:00
Francis Lachapelle c464791d92 Improve display of mail advanced search 2015-09-14 21:35:34 -04:00
Francis Lachapelle 92bf494973 (css) Improve display of folder name 2015-09-14 14:58:00 -04:00
Francis Lachapelle 47ab4b837c (js) Fix issues with the mail advanced search
- fixed loading of headers when scrolling the virtual repeater;
- fixed viewing a single message;
- fixed search from a mailbox that contains subfolders.
2015-09-14 14:58:00 -04:00
Ludovic Marcotte e30fb0d1fb (feat) basic (but empty) admin module for SOGo v3 2015-09-14 14:09:29 -04:00
Ludovic Marcotte 3efbf65f80 (fix) added missing test 2015-09-14 13:40:29 -04:00
Juan Vallés 0982d50643 Improve first character check in CSS identifiers
A CSS identifier can't start with a digit, so when a folder name does,
a '_' character is appended at the beginning of its CSS identifier.

The check for this first character used the `isdigit()` function, which
takes a `char` argument, while `[self objectAtIndex: 0]` returns a
`unichar`, i.e. a 16-bit unsigned integer. This caused some non-digit
characters to pass this check (e.g. Chinese characters), ending up with
an underscore at the beginning of the folder name.
2015-09-14 12:55:32 -04:00
Enrique J. Hernández Blasco 532105f1fa oc-task: Save tasks from Outlook
It was not working because we try to save as component the
full calendar and its parent was nil. We have to save the calendar
itself to save the task in the personal calendar.
2015-09-14 12:55:26 -04:00
Ludovic Marcotte 7cdfaf4d9c (fix) add missing flex attribute 2015-09-14 11:23:27 -04:00
Ludovic Marcotte 8cb19546df (fix) improve spec file regarding disabling OC build 2015-09-14 11:06:18 -04:00
Ludovic Marcotte 6dc02abe80 (fix) Start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344) 2015-09-14 10:56:14 -04:00
Francis Lachapelle f70876aa05 (html) Alternate conditions in topnav toolbar
Fixes an issue with Firefox
2015-09-14 10:14:00 -04:00
Francis Lachapelle 3efdbf0166 (js) Fix mdColors module dependencies 2015-09-11 12:24:30 -04:00
Francis Lachapelle 348c81bf6c (html) Fix display of Calendar module on "sm" 2015-09-11 12:08:18 -04:00
Francis Lachapelle 44158af057 (html) Fix display of avatars in invitation mails 2015-09-11 11:56:26 -04:00
Francis Lachapelle 4bb1fa0995 (html) Improve Task editor 2015-09-11 11:32:20 -04:00
Francis Lachapelle 05882362f0 (html) Fix UserRightsEditor templates 2015-09-11 11:30:54 -04:00
Francis Lachapelle 90a3fcacaf (js) Disable debug data if UIxDebug not enabled
Disable some Angular debugging information if SOGoUIxDebugEnabled is not
set.
2015-09-11 10:58:17 -04:00
Francis Lachapelle 45ac6845a5 (html) Improve appointment editor 2015-09-11 09:33:39 -04:00
Francis Lachapelle b4af6f5033 (css) Improve sgTimepicker 2015-09-11 09:32:17 -04:00
Ludovic Marcotte 6bbd182559 (fix) safe-guard against not-yet-created calendar of local users 2015-09-10 20:08:48 -04:00
Ludovic Marcotte 7290fa2ea2 (fix) disable oc builds on el6 for now 2015-09-10 19:45:59 -04:00
Ludovic Marcotte 788f36b96e (fix) slight modification for fix #3293 2015-09-10 19:11:37 -04:00
Ludovic Marcotte cb0d327f90 (feat) initial implementation of advanced email search
There's one bug where viewing of the messages from the results
is still impossible due to the ui-router. This will be fixed
very soon.
2015-09-10 16:38:30 -04:00
Francis Lachapelle b646e35f5d (js) Fix mail labels handling in the Mail module 2015-09-10 11:44:09 -04:00
Francis Lachapelle f3a5df37ae (js) Don't load card in multi-selection mode 2015-09-10 09:19:28 -04:00
Francis Lachapelle 7a7547c7c3 (js) mdColors module to expose the themes colors 2015-09-09 16:39:56 -04:00
Francis Lachapelle 11e7af3450 Adopt theming system of Angular Material
The SASS has been simplified so that different themes can be applied by
only using the $mdThemingProvider serivce.

The resulting design is more "Materialized" and less prone to the
future changes of Angular Material.
2015-09-09 16:35:09 -04:00
Francis Lachapelle 16d312eb98 (js) Don't load item in multi-selection mode
Also review the toolbars of the Calendar module.
2015-09-09 16:06:17 -04:00
Ludovic Marcotte 295b53df66 (fix) bumped to beta 2 2015-09-09 15:40:13 -04:00
Francis Lachapelle cab6a8c1e2 (html) Use md-subheader in contacts/messages lists 2015-09-09 14:43:09 -04:00
Ludovic Marcotte 59ad8a2e5a (fix) improved EAS speed during initial syncing of large mailboxes (#3293)
Conflicts:

	SoObjects/Mailer/SOGoMailFolder.h
	SoObjects/SOGo/SOGoGCSFolder.m
2015-09-09 10:22:29 -04:00
Ludovic Marcotte ab8feff67d (fix) avoid too-many LIST commands over EAS (#3294) 2015-09-09 10:13:27 -04:00
Ludovic Marcotte b9294da996 Updated NEWS file regarding PR#102 2015-09-09 08:25:23 -04:00
Ludovic Marcotte 18a300ca5b (fix) appplied fix from Zentyal - PR#102
Original commit: 33a1d2c397
2015-09-09 08:22:33 -04:00
Francis Lachapelle b8b45eda75 (js) Fix autocompletion of mail tags 2015-09-08 15:29:49 -04:00
Francis Lachapelle 370be4f835 (fix) Reload of message when saving draft 2015-09-08 12:22:37 -04:00
Francis Lachapelle 2720aebf04 Initial popup view for messages 2015-09-04 17:31:16 -04:00
Francis Lachapelle bf02b3046f (fix) Handle user's default for remote images 2015-09-04 16:43:01 -04:00
Francis Lachapelle 3d4a965412 (html) Use sgAvatarImage directive in Scheduler 2015-09-04 12:11:20 -04:00
Francis Lachapelle 372206e69e (html) Improve folder subscription dialog 2015-09-04 10:32:26 -04:00
Francis Lachapelle 9dc00acd53 (html) Fix toolbar padding 2015-09-04 10:31:40 -04:00
Francis Lachapelle 9d5f42eefa (js,html) Improve sgAvatarImage directive 2015-09-04 09:09:43 -04:00
Francis Lachapelle f4dcd37570 (html) Improve task editor 2015-09-04 08:53:47 -04:00
Francis Lachapelle c4858ffb2b (css) Decrease minimum height of list items 2015-09-03 22:36:07 -04:00
Francis Lachapelle d72188e2da Improve toolbars on small screens
- normalized toolbars in message viewer, contact viewer and contact
  editor;
- lower width of sidenav on medium screens;
- reworked display of message headers
2015-09-03 16:17:21 -04:00
Francis Lachapelle 3647f066da (js) Fix reference to org attribute in card 2015-09-03 15:51:55 -04:00
Francis Lachapelle e207cc79a7 (html) Improve appointment editor 2015-09-03 15:47:05 -04:00
Francis Lachapelle eb84e618e6 Localization 2015-09-03 15:42:54 -04:00
Francis Lachapelle 669bd99a2d (js) Fix bug when editing a saved draft 2015-09-03 14:11:39 -04:00
Francis Lachapelle 3b0ead7bbd (js) Cleanup unused dependencies 2015-09-03 14:11:39 -04:00
Francis Lachapelle 4483d47e89 (js) Bottom sheet is no longer used 2015-09-03 14:11:38 -04:00
Francis Lachapelle a5213b3321 (css) Improve subheaders with no buttons 2015-09-03 14:11:38 -04:00
Francis Lachapelle e6e34ae053 (js) Don't show progress indicator in dry mode 2015-09-03 14:11:37 -04:00
Francis Lachapelle 815b3b4b03 (html) Improve contact editor 2015-09-03 14:11:37 -04:00
Francis Lachapelle b08d98e2e1 (css) Fix width of sidenav on large screens 2015-09-03 14:11:36 -04:00
Francis Lachapelle 1d2c0ed350 (css) Cleanup Sass 2015-09-03 14:11:35 -04:00
Ludovic Marcotte 0bdbe5bbac (feat) added new sg-placeholder directive for dynamic placeholders 2015-09-03 13:25:44 -04:00
Francis Lachapelle acfa96263b (css) Improve md-dialog on small screens 2015-09-02 15:52:18 -04:00
Francis Lachapelle 99b1353faf (css) Add classes for star and star-border icons 2015-09-02 15:51:07 -04:00
Ludovic Marcotte 27e57e397e (fix) wrong handling of "last module" when logging in 2015-09-01 16:43:56 -04:00
Ludovic Marcotte 778612bf41 (feat) ensure links in mail are opened in a blank target 2015-09-01 15:00:03 -04:00
Ludovic Marcotte 9d92318524 (feat) autorefresh support for calendars 2015-09-01 10:50:50 -04:00
Francis Lachapelle cf9b7fab36 Update .gitignore 2015-09-01 09:55:55 -04:00
Ludovic Marcotte f1037e7d48 Updates for the 3.0b1 release. 2015-08-31 11:27:40 -04:00
Ludovic Marcotte 233b9c0613 (js/css) updates for the new packages 2015-08-31 11:08:41 -04:00
Francis Lachapelle 89206f3705 (css) Fix for latest md changes 2015-08-31 09:51:08 -04:00
Ludovic Marcotte a87daf282c (fix) workaround for Firefox with menus which include md-checkbox
See https://github.com/angular/material/issues/4212 for details
on the Angular Material bug.
2015-08-29 16:30:07 -04:00
Ludovic Marcotte fb4889a15e (fix) small code refactoring 2015-08-28 20:11:21 -04:00
Ludovic Marcotte 3fdabf8671 (fix) properly update flags when message was answered/forwarded 2015-08-28 15:50:46 -04:00
Ludovic Marcotte 66c52f22dd (fix) added timepicker directive 2015-08-28 15:26:21 -04:00
Francis Lachapelle 8f7334f11d (js) Simplify Account and Message services 2015-08-28 15:17:04 -04:00
Ludovic Marcotte 624833eca9 (fix) updates for new packages 2015-08-28 14:09:20 -04:00
Ludovic Marcotte 431c53b84d (feat) new time picker component + enabled it in the calendar module 2015-08-28 13:59:41 -04:00
Francis Lachapelle c9d9f7b47a (js) Add caching of cards 2015-08-27 15:55:56 -04:00
Francis Lachapelle 1667f8879d (js) New progress indicator in AddressBook module 2015-08-27 14:09:43 -04:00
Francis Lachapelle 3611c4021d Improve view detail (messages, cards) 2015-08-27 13:37:47 -04:00
Luc Charland bba0d6f44d make the tests more robust when data is invalid in DB/Webdav 2015-08-27 11:51:58 -04:00
Luc Charland bf390f17dd fixed when strings not initialized in prefs 2015-08-27 11:51:58 -04:00
Ludovic Marcotte 834aba85a6 (fix) cleanups 2015-08-27 11:47:51 -04:00
Ludovic Marcotte d878c69c15 (feat) can now create mails from address book module 2015-08-27 11:01:23 -04:00
Francis Lachapelle d9d59c6b52 (fix) Force a single mail composer dialog 2015-08-26 21:22:39 -04:00
Francis Lachapelle 69000a0929 Review animations of view detail (messages, cards) 2015-08-26 17:28:03 -04:00
Ludovic Marcotte 876d9cfa21 (fix) make messages loading very fast
Avoid calling angular.extend() loading when unwrapping the mailbox
and never call again Message.id directly, but rather use Message.$absolutePath()
which does initialization and caching
2015-08-26 11:56:47 -04:00
Luc Charland 117e6b6bc1 added the multiple phone test 2015-08-26 11:28:54 -04:00
Francis Lachapelle 01e2256483 (css) Improve autocompletion menu 2015-08-26 09:48:47 -04:00
Francis Lachapelle 49e7cf0994 (css) Adapt fullscreen dialogs to new md layout 2015-08-26 09:41:49 -04:00
Ludovic Marcotte b3d6973fb1 (fix) small optimization 2015-08-26 09:31:08 -04:00
Ludovic Marcotte aae522259f (fix) expose the json accounts call 2015-08-26 08:28:19 -04:00
Ludovic Marcotte 68ef8784a0 (fix) added json call to get all mail accounts 2015-08-26 08:27:45 -04:00
Francis Lachapelle da821ea6c7 (js) Improve recipient auto-completion 2015-08-25 22:17:57 -04:00
Ludovic Marcotte 4dee99e0ba (fix) improved padding usage to better align icons 2015-08-25 20:47:12 -04:00
Francis Lachapelle eea08d083d (html) Improve contacts list 2015-08-25 14:56:30 -04:00
Francis Lachapelle 8055acfd4a (fix) Restore previous sort in Contacts module 2015-08-25 14:20:20 -04:00
Francis Lachapelle 1fc947d8c8 (fix) Define CSS styles for ALL calendars 2015-08-25 13:46:33 -04:00
Francis Lachapelle 7b3a238fb9 (js) New progress indicator in Mail module 2015-08-25 12:20:46 -04:00
Ludovic Marcotte 99d121fd3e (fix) updates for new packages 2015-08-25 11:46:14 -04:00
Ludovic Marcotte eab3ee111a (feat) now use md-on-demand for (hopefully) faster mailbox loading 2015-08-25 11:45:17 -04:00
Luc Charland 9445315b30 Added a string, removed a double, removed a few colons 2015-08-25 11:11:46 -04:00
Francis Lachapelle eeb6be1f08 (css) Match new ng-material files names 2015-08-25 11:01:28 -04:00
Luc Charland c106f54b27 Added missing strings for translation 2015-08-25 10:45:47 -04:00
Francis Lachapelle 29108bbb62 (html) Localization 2015-08-25 06:34:34 -04:00
Ludovic Marcotte 9c6884bd7d (feat) add single export/save as actions 2015-08-24 20:04:56 -04:00
Francis Lachapelle 0aac4897fa (fix) Set virtual item size in contacts list 2015-08-24 17:28:01 -04:00
Ludovic Marcotte 2a99d1fd80 (fix) reset search filter when switching address books 2015-08-24 16:17:44 -04:00
Francis Lachapelle ba148eec5b (js) Fix error in Component.prototype.$reset 2015-08-24 14:54:25 -04:00
Francis Lachapelle 49c4884cc9 (html) Improve grids layout in recurrence editor 2015-08-24 14:40:20 -04:00
Francis Lachapelle e5d6ee93c3 Update Angular to version 1.4.x 2015-08-24 11:32:40 -04:00
Ludovic Marcotte 40eff05d4e (feat) added toolbar to account editing dialog 2015-08-24 11:26:00 -04:00
Francis Lachapelle be031fe2c9 (html) Restore animation when opening a card 2015-08-24 11:24:51 -04:00
Francis Lachapelle 0fe1419289 (html) Enlarge folder links dialog width 2015-08-24 11:23:34 -04:00
Francis Lachapelle 9db633cada (html) Replace md-layout-fill by layout-fill 2015-08-24 11:21:17 -04:00
Ludovic Marcotte 8a2d7d56e0 (feat) added toolbar to filter editor dialog 2015-08-24 11:20:23 -04:00
Francis Lachapelle 0c17e745f1 (html) Localization 2015-08-24 11:11:23 -04:00
Ludovic Marcotte 18bc75e50b (fix) adjust import path due to md changes 2015-08-24 08:49:31 -04:00
Ludovic Marcotte 68ad3c1045 (fix) localize 'me' 2015-08-22 07:51:13 -04:00
Ludovic Marcotte 85c8aebbd4 (feat) initial support for contact import in the address book module 2015-08-21 20:56:21 -04:00
Ludovic Marcotte 0a8834193f (fix) updates for new packages 2015-08-21 13:40:07 -04:00
Ludovic Marcotte 91e4c2cefc (feat) improved the mailviewer with regards to sender + recipients 2015-08-21 13:37:18 -04:00
Ludovic Marcotte b80d8dbb06 (fix) Session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less 2015-08-21 10:51:45 -04:00
Ludovic Marcotte 525d1ad80f (fix) also return all calendars in findAll 2015-08-20 22:38:01 -04:00
Ludovic Marcotte 332dd0d96d (fix) small optimization 2015-08-20 22:37:27 -04:00
Ludovic Marcotte a45000d15d (fix) show delete option only where objectEraser is on 2015-08-20 19:42:24 -04:00
Ludovic Marcotte 3301bdb055 (fix) return the complete list of addressbooks
This fixes many bugs / side effects, especially with ACLs
2015-08-20 19:37:30 -04:00
Francis Lachapelle 2f82b5cfd3 (js) Localization of toast message 2015-08-20 16:39:34 -04:00
Francis Lachapelle e3b64e035d (css) New md-fab-overlap-bottom class 2015-08-20 16:28:11 -04:00
Francis Lachapelle 1470258b5c (js) Simplify Account.prototype.$newMailbox 2015-08-20 16:28:11 -04:00
Francis Lachapelle 83fee27ca8 (js) Simplify Mailbox.prototype.$emptyTrash 2015-08-20 16:28:10 -04:00
Ludovic Marcotte e5b0596bc9 (fix) don't convert back the birthday date object on non-deep omits 2015-08-20 15:47:41 -04:00
Ludovic Marcotte 26e8276b08 (feat) improved ACLs handling for address book 2015-08-20 15:47:11 -04:00
Ludovic Marcotte 078ba95f8e (feat) show small toast when preferences are saved to give user's feedback 2015-08-20 14:53:43 -04:00
Ludovic Marcotte 65525dd33b (feat) added date picker in contact editor + now handle correctly birthday 2015-08-20 10:29:47 -04:00
Ludovic Marcotte da81f7bc58 (fix) set background to transparent by default 2015-08-19 20:45:07 -04:00
Ludovic Marcotte 8fcd594be6 (fix) removed unused code 2015-08-19 19:38:42 -04:00
Ludovic Marcotte 548f056b9c (fix) small formatting nit 2015-08-19 11:23:03 -04:00
Ludovic Marcotte dffdbf318c (feat) new 'viewable' property on events/tasks for full viewers 2015-08-19 11:22:17 -04:00
Ludovic Marcotte 161bcfac87 (fix) proper loading of transalations 2015-08-19 10:39:10 -04:00
Ludovic Marcotte c6f17cd45c (feat) make use of the sieve capabilities 2015-08-19 08:47:33 -04:00
Ludovic Marcotte 38713a6c67 (feat) handle multiple home/work phones, emails and urls 2015-08-18 16:21:47 -04:00
Ludovic Marcotte 8cef98eba1 (fix) add flex/flex-sm attributes 2015-08-18 15:32:44 -04:00
Ludovic Marcotte 2144d3122a (feat) added support back for SOGoForwardConstraints 2015-08-18 15:25:51 -04:00
Ludovic Marcotte 9d272ae063 (feat) added the 'add default email addresses' feature in vacation module 2015-08-18 14:57:29 -04:00
Ludovic Marcotte fcd0140ffd Revert "(fix) by default, open links in new tab/window from html mails"
This reverts commit 869b3c4cd1.
2015-08-18 14:52:47 -04:00
Ludovic Marcotte d783782af7 (fix) adding basic flags 2015-08-18 11:31:42 -04:00
Ludovic Marcotte 869b3c4cd1 (fix) by default, open links in new tab/window from html mails 2015-08-18 11:13:27 -04:00
Ludovic Marcotte 2da2937391 (fix) formatting improvements 2015-08-18 11:01:59 -04:00
Ludovic Marcotte 5034d6655a (fix) correctly refresh the mailboxes list when empty'ing the trash that contains subfolders 2015-08-18 10:10:37 -04:00
Ludovic Marcotte d1446f7dc1 (fix) broken top level mailbox creation 2015-08-18 09:57:07 -04:00
Ludovic Marcotte c5a219add8 (fix) wrong check for web calendars 2015-08-18 09:27:33 -04:00
Ludovic Marcotte bc2c839fbc Update for the new packaging 2015-08-18 08:39:20 -04:00
Ludovic Marcotte 2e736490d5 Merge branch 'master' of https://github.com/inverse-inc/sogo 2015-08-17 16:22:41 -04:00
Ludovic Marcotte 20bf66d931 (feat) handle links to addressbooks like we do for calendars
also included missing template commit for calendar
2015-08-17 16:21:10 -04:00
Francis Lachapelle 864a7f8028 (html) Improve Preferences module 2015-08-17 16:14:47 -04:00
Ludovic Marcotte 7b5541b5d1 (fix) reuse the calendar urls from the model 2015-08-17 13:44:21 -04:00
Ludovic Marcotte 744cba4422 (feat) integrated md-datepicker and enabled it in the Preferences module (Vacation)
The date picker suffers from this bug: https://github.com/angular/material/issues/4192

Which makes it not that usable right now.
2015-08-17 10:10:57 -04:00
Ludovic Marcotte ded467e0a2 (fix) add empty settings hashes to prever JS exceptions 2015-08-17 09:55:14 -04:00
Ludovic Marcotte a718b87d91 (fix) minor gui improvement to links templates 2015-08-16 21:42:53 -04:00
Ludovic Marcotte 6e2e71b202 (fix) correctly set flex attribut on sm devices 2015-08-15 17:25:04 -04:00
Ludovic Marcotte 04e63e5edf (fix) add minimal constraints when creating events or tasks 2015-08-15 17:08:15 -04:00
Ludovic Marcotte f1a50bcb74 (feat) added refresh support to calendar 2015-08-14 16:31:29 -04:00
Ludovic Marcotte 0672b96b8b (fix) show the alarm name as a floating label for now
This might change as the text is so tiny!
2015-08-14 15:51:17 -04:00
Ludovic Marcotte 93fa74d279 (feat) save/restore sorting state for address books
The initial loading doesn't work yet. Will work on this later.
2015-08-14 15:22:59 -04:00
Ludovic Marcotte ffa4079fa8 (fix) b0rk3n sort saving regarding the asc/desc state 2015-08-14 15:11:07 -04:00
Luc Charland 8f75bdce2d Use Json for testing webcalendar 2015-08-14 12:46:37 -04:00
Luc Charland 4f53ea70e1 Commented out testing two phones same type until fixed 2015-08-14 12:46:03 -04:00
Ludovic Marcotte b72a9383a4 (feat) major rework of the sidenav and top toolbars 2015-08-14 12:14:15 -04:00
extrafu d3d9a720dc Merge pull request #99 from inverse-inc/feature/saveSortContacts
Added sort saving to Contacts
2015-08-13 15:29:59 -04:00
Ludovic Marcotte 02daebe36b (fix) also handle tasks deletion correctly 2015-08-12 18:31:06 -04:00
Ludovic Marcotte 336e4c93e9 (fix) added temporary fix for file inputs on firefox 2015-08-12 18:25:15 -04:00
Ludovic Marcotte 47252affba (feat) initial selection + ops in calendar module 2015-08-12 16:11:38 -04:00
Luc Charland 8fb5c1d09f Added sort saving to Contacts 2015-08-12 14:39:26 -04:00
Ludovic Marcotte 54334b294e (fix) small fix in the template 2015-08-12 10:31:06 -04:00
Ludovic Marcotte c51bed2580 (fix) updated js/css/map files for packaging 2015-08-12 09:43:55 -04:00
Ludovic Marcotte 935df2bd00 (fix) wrong label name 2015-08-11 21:16:16 -04:00
Ludovic Marcotte 85a843bc1e (fix) now show the event/task list instead of the day/week/month views on sm devices 2015-08-11 20:58:39 -04:00
Ludovic Marcotte 723372dddb (fix) when batch deleting, update the unseen count accordingly 2015-08-11 15:05:31 -04:00
Ludovic Marcotte 9362513fee (fix) moved the mailbox cleanup after expunge in the service 2015-08-11 14:48:45 -04:00
Ludovic Marcotte 2e3057c2ce (fix) calendar views now honor "breakpoints"
We also now use again the default top nav toolbar and we
hide the fab button on sm devices. This of course needs a bit
more work but we are getting there!
2015-08-11 13:40:38 -04:00
Ludovic Marcotte 456649a38a (fix) allow fullsreen editor on sm devices 2015-08-11 13:38:28 -04:00
Ludovic Marcotte 86818d4f64 (fix) fixed scrolling during contact edition 2015-08-11 13:10:11 -04:00
Ludovic Marcotte b59287d6df (fix) removed unused code 2015-08-11 11:18:41 -04:00
Ludovic Marcotte bdc8dfb2b4 (fix) removed unused code 2015-08-11 09:38:53 -04:00
Ludovic Marcotte 2825285b72 (fix) author cleanup 2015-08-11 08:45:05 -04:00
Ludovic Marcotte 314dcfd30e (fix) properly cleanup the Trash folder after empty'ing 2015-08-10 23:48:59 -04:00
Ludovic Marcotte d28fccf71e (fix) open attachment in new window 2015-08-10 23:14:40 -04:00
Ludovic Marcotte ff632ec3e8 (fix) leave some room for the message date 2015-08-10 22:02:43 -04:00
Ludovic Marcotte b7af83f7e3 (feat) pimped the login window 2015-08-10 20:47:58 -04:00
Ludovic Marcotte d2aeee39ef (feat) now able to rename folders w/o double-tap 2015-08-10 14:13:25 -04:00
Ludovic Marcotte ecc0c453d7 (fix) pimped the priority editor 2015-08-10 13:27:35 -04:00
Ludovic Marcotte 420dece1e7 (feat) flag selected messages and mark selected messages as unread 2015-08-10 13:10:45 -04:00
Ludovic Marcotte c738a2dc70 (fix) wrong use of this which was breaking ACL module 2015-08-10 11:02:23 -04:00
Ludovic Marcotte e41e17c5d9 (fix) use proper var:string value 2015-08-10 09:54:14 -04:00
Ludovic Marcotte 5741d7f95c (feat) can now copy selected cards 2015-08-10 09:48:47 -04:00
Ludovic Marcotte e13f7a267b (feat) show email priority 2015-08-09 20:21:09 -04:00
Ludovic Marcotte dd7b929f17 (feat) compose mail with clicked email address 2015-08-09 10:36:54 -04:00
Ludovic Marcotte ad9b969d6d (fix) avoid displaying the chips line 2015-08-07 21:14:17 -04:00
Ludovic Marcotte 704307e6d6 (feat) pimped the mail part viewers 2015-08-07 20:38:26 -04:00
Francis Lachapelle ce8a4b6d8a (js) Make events clickable in month view 2015-08-07 15:32:00 -04:00
Francis Lachapelle 8ab591a1b6 (html) Improve task viewer 2015-08-07 14:25:57 -04:00
Francis Lachapelle 4434547a18 (css) Don't highlight disabled list items 2015-08-07 14:25:14 -04:00
Francis Lachapelle 29d86ad478 (js) Respect user's settings in Calendar module 2015-08-07 14:15:26 -04:00
Ludovic Marcotte 15a5ab0b12 (fix) added tooltip for consistency 2015-08-07 13:46:01 -04:00
Francis Lachapelle 3b5013555d (html) Improve/fix appointment/task viewer/editor 2015-08-07 11:22:24 -04:00
Francis Lachapelle 655a499cd4 (css) Fix subheader margin 2015-08-07 11:18:40 -04:00
Francis Lachapelle 52559087b3 (css) Merge autoScrollList.scss with list.scss 2015-08-07 11:17:09 -04:00
Francis Lachapelle fb7d5b628f (js) Make use of 'controllerAs' in MainUI module 2015-08-07 11:04:04 -04:00
Francis Lachapelle 6e82a7ba7b (js) Indentation 2015-08-07 11:02:34 -04:00
Ludovic Marcotte fbcaf25428 Bumped to v3.0.0 2015-08-07 10:46:45 -04:00
Ludovic Marcotte a17e59af58 (feat) added v3 js/css/map files for packaging 2015-08-07 10:37:36 -04:00
Francis Lachapelle b966fd3e30 (js) Move *.app.js files to their directories 2015-08-07 09:56:47 -04:00
Ludovic Marcotte caa50d25cb (fix) removed broken parts from the template 2015-08-07 09:49:51 -04:00
Francis Lachapelle 636e5c375e (js) Merge utils.js to Common.js 2015-08-07 09:38:21 -04:00
Ludovic Marcotte f6ea2229f8 (fix) reworked the targets to not always generate static files 2015-08-07 09:15:38 -04:00
Francis Lachapelle 7f2ef967b4 Merge pull request #97 from inverse-inc/feature/saveFilterEventsTasks
Reworked saving Events filter and added Tasks filter
2015-08-06 17:49:03 -04:00
Francis Lachapelle 063e973ef3 (css) Cleanup variables already in material 2015-08-06 17:39:01 -04:00
Francis Lachapelle eb543ce14f (js) Allow to change the labels of Dialog.confirm 2015-08-06 17:39:00 -04:00
Francis Lachapelle 9465fbad4b (obj-c) Indentation 2015-08-06 17:39:00 -04:00
Ludovic Marcotte 560feb45d9 (fix) massively pimped the email composer 2015-08-06 16:18:53 -04:00
Ludovic Marcotte b5ca697b43 (fix) imprived the gravatar directive to extract email address between < > 2015-08-06 16:18:10 -04:00
Ludovic Marcotte 33d0f30d97 (fix) avoid closing the email composition window by error 2015-08-06 16:17:31 -04:00
Francis Lachapelle 0d537ab27b Return recurrence rules of occurrence 2015-08-06 16:15:12 -04:00
Francis Lachapelle 2239286697 (js) Use CK Editor in mail account editor (Prefs) 2015-08-06 16:13:10 -04:00
Francis Lachapelle d0d79728d2 (js) Set locale to CKEditor in Mail editor 2015-08-06 16:09:49 -04:00
Francis Lachapelle e6c9289784 Add LocaleCode to JSON defaults 2015-08-06 16:07:46 -04:00
Francis Lachapelle ae017b50bc (js) Improve ckEditor directive
The directive will now consider the following attributes:

- ck-options: a JSON object of options
- ck-locale: the locale code to be used as the language and the speller
  language
2015-08-06 16:04:39 -04:00
Luc Charland 42eb9e51b4 Reworked saving Events filter and added Tasks filter 2015-08-06 14:28:54 -04:00
Francis Lachapelle 8690cee221 Merge pull request #96 from inverse-inc/feature/saveSortEventsTasks
Feature/save sort events tasks
2015-08-06 14:26:33 -04:00
Luc Charland 7bb551a4ad Added saving of sort order for Events and Tasks 2015-08-06 13:48:39 -04:00
922 changed files with 135847 additions and 21541 deletions

No files matched your search

+5 -6
View File
@@ -9,6 +9,7 @@
*/*/obj/
*/obj/
.scss-lint-config.yml_
ActiveSync/ActiveSync.SOGo
Documentation/*.docbook
Documentation/*.pdf
SoObjects/SOGo/SOGo.framework/
@@ -20,12 +21,10 @@ UI/WebServerResources/bower_components/
UI/WebServerResources/css/
UI/WebServerResources/css/styles.css
UI/WebServerResources/css/styles.css.map
UI/WebServerResources/js/Common.js*
UI/WebServerResources/js/Contacts.js*
UI/WebServerResources/js/Mailer.js*
UI/WebServerResources/js/Preferences.js*
UI/WebServerResources/js/Scheduler.js*
UI/WebServerResources/js/vendor/
UI/WebServerResources/js/*.js
UI/WebServerResources/js/*.js.map
UI/WebServerResources/js/vendor/*.js
UI/WebServerResources/js/vendor/*.js.map
UI/WebServerResources/node_modules/
UI/WebServerResources/scss/.sass-cache/
config.make
+33
View File
@@ -15,13 +15,16 @@ trans.es_ES = UI/MailerUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MailerUI/Basque.lproj/Localizable.strings
trans.fi = UI/MailerUI/Finnish.lproj/Localizable.strings
trans.fr = UI/MailerUI/French.lproj/Localizable.strings
trans.hr_HR = UI/MailerUI/Croatian.lproj/Localizable.strings
trans.hu = UI/MailerUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MailerUI/Icelandic.lproj/Localizable.strings
trans.it = UI/MailerUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/MailerUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MailerUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MailerUI/Polish.lproj/Localizable.strings
trans.pt = UI/MailerUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MailerUI/Russian.lproj/Localizable.strings
trans.sk = UI/MailerUI/Slovak.lproj/Localizable.strings
@@ -44,13 +47,16 @@ trans.es_ES = UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/PreferencesUI/Basque.lproj/Localizable.strings
trans.fi = UI/PreferencesUI/Finnish.lproj/Localizable.strings
trans.fr = UI/PreferencesUI/French.lproj/Localizable.strings
trans.hr_HR = UI/PreferencesUI/Croatian.lproj/Localizable.strings
trans.hu = UI/PreferencesUI/Hungarian.lproj/Localizable.strings
trans.is = UI/PreferencesUI/Icelandic.lproj/Localizable.strings
trans.it = UI/PreferencesUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/PreferencesUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/PreferencesUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/PreferencesUI/Polish.lproj/Localizable.strings
trans.pt = UI/PreferencesUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/PreferencesUI/Russian.lproj/Localizable.strings
trans.sk = UI/PreferencesUI/Slovak.lproj/Localizable.strings
@@ -73,13 +79,16 @@ trans.es_ES = UI/Scheduler/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Scheduler/Basque.lproj/Localizable.strings
trans.fi = UI/Scheduler/Finnish.lproj/Localizable.strings
trans.fr = UI/Scheduler/French.lproj/Localizable.strings
trans.hr_HR = UI/Scheduler/Croatian.lproj/Localizable.strings
trans.hu = UI/Scheduler/Hungarian.lproj/Localizable.strings
trans.is = UI/Scheduler/Icelandic.lproj/Localizable.strings
trans.it = UI/Scheduler/Italian.lproj/Localizable.strings
trans.mk_MK = UI/Scheduler/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Scheduler/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Scheduler/Polish.lproj/Localizable.strings
trans.pt = UI/Scheduler/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Scheduler/Russian.lproj/Localizable.strings
trans.sk = UI/Scheduler/Slovak.lproj/Localizable.strings
@@ -102,13 +111,16 @@ trans.es_ES = UI/Contacts/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Contacts/Basque.lproj/Localizable.strings
trans.fi = UI/Contacts/Finnish.lproj/Localizable.strings
trans.fr = UI/Contacts/French.lproj/Localizable.strings
trans.hr_HR = UI/Contacts/Croatian.lproj/Localizable.strings
trans.hu = UI/Contacts/Hungarian.lproj/Localizable.strings
trans.is = UI/Contacts/Icelandic.lproj/Localizable.strings
trans.it = UI/Contacts/Italian.lproj/Localizable.strings
trans.mk_MK = UI/Contacts/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Contacts/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Contacts/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Contacts/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Contacts/Polish.lproj/Localizable.strings
trans.pt = UI/Contacts/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Contacts/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Contacts/Russian.lproj/Localizable.strings
trans.sk = UI/Contacts/Slovak.lproj/Localizable.strings
@@ -131,13 +143,16 @@ trans.es_ES = UI/MainUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MainUI/Basque.lproj/Localizable.strings
trans.fi = UI/MainUI/Finnish.lproj/Localizable.strings
trans.fr = UI/MainUI/French.lproj/Localizable.strings
trans.hr_HR = UI/MainUI/Croatian.lproj/Localizable.strings
trans.hu = UI/MainUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MainUI/Icelandic.lproj/Localizable.strings
trans.it = UI/MainUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/MainUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MainUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MainUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MainUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MainUI/Polish.lproj/Localizable.strings
trans.pt = UI/MainUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MainUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MainUI/Russian.lproj/Localizable.strings
trans.sk = UI/MainUI/Slovak.lproj/Localizable.strings
@@ -160,13 +175,16 @@ trans.es_ES = UI/Common/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Common/Basque.lproj/Localizable.strings
trans.fi = UI/Common/Finnish.lproj/Localizable.strings
trans.fr = UI/Common/French.lproj/Localizable.strings
trans.hr_HR = UI/Common/Croatian.lproj/Localizable.strings
trans.hu = UI/Common/Hungarian.lproj/Localizable.strings
trans.is = UI/Common/Icelandic.lproj/Localizable.strings
trans.it = UI/Common/Italian.lproj/Localizable.strings
trans.mk_MK = UI/Common/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Common/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Common/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Common/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Common/Polish.lproj/Localizable.strings
trans.pt = UI/Common/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Common/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Common/Russian.lproj/Localizable.strings
trans.sk = UI/Common/Slovak.lproj/Localizable.strings
@@ -189,13 +207,16 @@ trans.es_ES = UI/AdministrationUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/AdministrationUI/Basque.lproj/Localizable.strings
trans.fi = UI/AdministrationUI/Finnish.lproj/Localizable.strings
trans.fr = UI/AdministrationUI/French.lproj/Localizable.strings
trans.hr_HR = UI/AdministrationUI/Croatian.lproj/Localizable.strings
trans.hu = UI/AdministrationUI/Hungarian.lproj/Localizable.strings
trans.is = UI/AdministrationUI/Icelandic.lproj/Localizable.strings
trans.it = UI/AdministrationUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/AdministrationUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/AdministrationUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/AdministrationUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/AdministrationUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/AdministrationUI/Polish.lproj/Localizable.strings
trans.pt = UI/AdministrationUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/AdministrationUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/AdministrationUI/Russian.lproj/Localizable.strings
trans.sk = UI/AdministrationUI/Slovak.lproj/Localizable.strings
@@ -218,13 +239,16 @@ trans.es_ES = SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Appointments/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Appointments/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Appointments/French.lproj/Localizable.strings
trans.hr_HR = SoObjects/Appointments/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Appointments/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Appointments/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Appointments/Italian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Appointments/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Appointments/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Appointments/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Appointments/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Appointments/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Appointments/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Appointments/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Appointments/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Appointments/Slovak.lproj/Localizable.strings
@@ -247,13 +271,16 @@ trans.es_ES = SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Contacts/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Contacts/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Contacts/French.lproj/Localizable.strings
trans.hr_HR = SoObjects/Contacts/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Contacts/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Contacts/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Contacts/Italian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Contacts/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Contacts/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Contacts/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Contacts/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Contacts/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Contacts/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Contacts/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Contacts/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Contacts/Slovak.lproj/Localizable.strings
@@ -276,13 +303,16 @@ trans.es_ES = SoObjects/Mailer/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Mailer/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Mailer/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Mailer/French.lproj/Localizable.strings
trans.hr_HR = SoObjects/Mailer/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Mailer/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Mailer/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Mailer/Italian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Mailer/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Mailer/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Mailer/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Mailer/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Mailer/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Mailer/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Mailer/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Mailer/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Mailer/Slovak.lproj/Localizable.strings
@@ -305,13 +335,16 @@ trans.es_ES = UI/MailPartViewers/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MailPartViewers/Basque.lproj/Localizable.strings
trans.fi = UI/MailPartViewers/Finnish.lproj/Localizable.strings
trans.fr = UI/MailPartViewers/French.lproj/Localizable.strings
trans.hr_HR = UI/MailPartViewers/Croatian.lproj/Localizable.strings
trans.hu = UI/MailPartViewers/Hungarian.lproj/Localizable.strings
trans.is = UI/MailPartViewers/Icelandic.lproj/Localizable.strings
trans.it = UI/MailPartViewers/Italian.lproj/Localizable.strings
trans.mk_MK = UI/MailPartViewers/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MailPartViewers/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MailPartViewers/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MailPartViewers/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MailPartViewers/Polish.lproj/Localizable.strings
trans.pt = UI/MailPartViewers/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MailPartViewers/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MailPartViewers/Russian.lproj/Localizable.strings
trans.sk = UI/MailPartViewers/Slovak.lproj/Localizable.strings
+4 -1
View File
@@ -1 +1,4 @@
# compilation settings
ifeq ($(HAS_LIBRARY_ssl),yes)
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
BUNDLE_LIBS += -lcrypto
endif
+1 -1
View File
@@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
int i, count;
if (!asElementArray)
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
data = [NSMutableDictionary dictionary];
+181 -49
View File
@@ -48,11 +48,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation NGVCard (ActiveSync)
//
// This function is called for each elements which can be ghosted according to specs.
// https://msdn.microsoft.com/en-us/library/gg650908%28v=exchg.80%29.aspx
//
- (BOOL) _isGhosted: (NSString *) element
inContext: (WOContext *) context
{
NSArray *supportedElements;
supportedElements = [context objectForKey: @"SupportedElements"];
// If the client does not include a Supported element in the initial Sync command request for
// a folder, then all of the elements that can be ghosted are considered not ghosted.
if (!supportedElements)
return NO;
// If the client includes an empty Supported element in the initial Sync command request for
// a folder, then all elements that can be ghosted are considered ghosted.
if (![supportedElements count])
return YES;
// If the client includes a Supported element that contains child elements in the initial
// Sync command request for a folder, then each child element of that Supported element is
// considered not ghosted. All elements that can be ghosted that are not included as child
// elements of the Supported element are considered ghosted.
if (!([supportedElements indexOfObject: element] == NSNotFound))
return YES;
return NO;
}
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{
NSArray *emails, *addresses, *categories, *elements;
CardElement *n, *homeAdr, *workAdr;
NSMutableString *s;
NSMutableString *s, *a;
NSString *url;
id o;
@@ -63,7 +94,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
@@ -146,16 +177,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count])
{
homeAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [o activeSyncRepresentationInContext: context]];
[a appendString: o];
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", o];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
@@ -171,9 +208,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count])
{
workAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [o activeSyncRepresentationInContext: context]];
[a appendString: o];
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", o];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
@@ -217,6 +260,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inContext: (WOContext *) context
{
CardElement *element;
NSMutableArray *addressLines;
id o;
// Contact's note
@@ -226,6 +270,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Categories
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
[self setCategories: o];
else
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
// Birthday
if ((o = [theValues objectForKey: @"Birthday"]))
@@ -233,6 +279,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
o = [o calendarDate];
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
}
else if (![self _isGhosted: @"Birthday" inContext: context])
{
[self setBday: @""];
}
//
// Business address information
@@ -244,18 +295,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// BusinessCountry
//
element = [self elementWithTag: @"adr" ofType: @"work"];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"]
atIndex: 2 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
atIndex: 4 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
atIndex: 6 forKey: @""];
if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" inContext: context])
{
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
atIndex: 2 forKey: @""];
// Extended address line. If there are more than 2 address lines we add them to the extended address line.
if ([addressLines count] > 1)
{
[addressLines removeObjectAtIndex: 0];
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
atIndex: 1 forKey: @""];
}
}
if ((o = [theValues objectForKey: @"BusinessCity"]) || ![self _isGhosted: @"BusinessCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessState"]) || ![self _isGhosted: @"BusinessState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessPostalCode"]) || ![self _isGhosted: @"BusinessPostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessCountry"]) || ![self _isGhosted: @"BusinessCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
atIndex: 6 forKey: @""];
}
//
// Home address information
@@ -267,35 +348,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// HomeCountry
//
element = [self elementWithTag: @"adr" ofType: @"home"];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeStreet"]
atIndex: 2 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeState"]
atIndex: 4 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
atIndex: 6 forKey: @""];
if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" inContext: context])
{
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
atIndex: 2 forKey: @""];
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
if ([addressLines count] > 1)
{
[addressLines removeObjectAtIndex: 0];
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
atIndex: 1 forKey: @""];
}
}
if ((o = [theValues objectForKey: @"HomeCity"]) || ![self _isGhosted: @"HomeCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomeState"]) || ![self _isGhosted: @"HomeState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomePostalCode"]) || ![self _isGhosted: @"HomePostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomeCountry"]) || ![self _isGhosted: @"HomeCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
atIndex: 6 forKey: @""];
}
// Company's name
if ((o = [theValues objectForKey: @"CompanyName"]))
[self setOrg: o units: nil];
else if (![self _isGhosted: @"CompanyName" inContext: context])
[self setOrg: @"" units: nil];
// Department
if ((o = [theValues objectForKey: @"Department"]))
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
else if (![self _isGhosted: @"Department" inContext: context])
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
// Email addresses
if ((o = [theValues objectForKey: @"Email1Address"]))
if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"work"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
}
if ((o = [theValues objectForKey: @"Email2Address"]))
if ((o = [theValues objectForKey: @"Email2Address"]) || ![self _isGhosted: @"Email2Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"home"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
@@ -303,7 +418,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
// FIXME: revise this when the GUI revamp is done in SOGo
if ((o = [theValues objectForKey: @"Email3Address"]))
if ((o = [theValues objectForKey: @"Email3Address"]) || ![self _isGhosted: @"Email3Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"three"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
@@ -313,45 +428,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// MiddleName
// Suffix (II)
// Title (Mr.)
[self setFn: [theValues objectForKey: @"FileAs"]];
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
[self setFn: [theValues objectForKey: @"FileAs"]];
[self setNWithFamily: [theValues objectForKey: @"LastName"]
given: [theValues objectForKey: @"FirstName"]
additional: nil prefixes: nil suffixes: nil];
// IM information
[[self uniqueChildWithTag: @"x-aim"]
setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
[[self uniqueChildWithTag: @"x-aim"]
setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
//
// Phone numbrrs
//
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
}
// Job's title
if ((o = [theValues objectForKey: @"JobTitle"]))
if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
[self setTitle: o];
// WebPage (work)
if ((o = [theValues objectForKey: @"WebPage"]))
if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
[[self elementWithTag: @"url" ofType: @"work"]
setSingleValue: o forKey: @""];
if ((o = [theValues objectForKey: @"NickName"]))
if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
[self setNickname: o];
if ((o = [theValues objectForKey: @"Picture"]))
+1 -1
View File
@@ -75,7 +75,7 @@ static NSArray *easCommandParameters = nil;
escapeCount++;
break;
default:
if (chars[i] > 127)
if (chars[i] < 0x20 || chars[i] > 127)
escapeCount++;
break;
}
+479 -114
View File
@@ -78,6 +78,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoCacheGCSObject.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentObject.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentFolders.h>
@@ -112,13 +113,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher (Sync)
- (void) _setOrUnsetSyncRequest: (BOOL) set
collections: (NSArray *) collections
{
SOGoCacheGCSObject *o;
NSNumber *processIdentifier;
NSString *key;
int i;
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
if (set)
{
RELEASE(syncRequest);
syncRequest = [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]];
RETAIN(syncRequest);
[[o properties] setObject: syncRequest forKey: @"SyncRequest"];
for (i = 0; i < [collections count]; i++)
{
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
[[o properties] setObject: processIdentifier forKey: key];
}
}
else
{
[[o properties] removeObjectForKey: @"SyncRequest"];
for (i = 0; i < [collections count]; i++)
{
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
[[o properties] removeObjectForKey: key];
}
}
[o save];
}
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
forKey: (NSString *) theFolderKey
{
NSNumber *processIdentifier, *processIdentifierInCache;
SOGoCacheGCSObject *o;
NSDictionary *values;
NSString *key;
if ([theFolderKey hasPrefix: @"folder"])
key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]];
else
key = [NSString stringWithFormat: @"SyncRequest+%@", theFolderKey];
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
// Don't update the cache if another request is processing the same collection.
if (!([processIdentifierInCache isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier];
return;
}
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
values = [theFolderMetadata copy];
@@ -132,7 +193,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
[[o properties] addEntriesFromDictionary: values];
[o save];
@@ -159,7 +222,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSString *nameInCache;
if (theFolderType == ActiveSyncMailFolder)
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
else
{
NSString *component_name;
@@ -170,7 +233,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else
component_name = @"vtodo";
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
}
return nameInCache;
@@ -403,38 +466,47 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
}
break;
case ActiveSyncEventFolder:
case ActiveSyncTaskFolder:
{
o = [sogoObject component: NO secure: NO];
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
if (theFolderType == ActiveSyncEventFolder &&
[(iCalEvent *)o userIsAttendee: [context activeUser]])
{
[o changeParticipationStatus: allChanges inContext: context component: sogoObject];
}
else
{
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
}
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
}
break;
case ActiveSyncMailFolder:
default:
{
NSDictionary *result;
NSString *modseq;
NSNumber *modseq;
[sogoObject takeActiveSyncValues: allChanges inContext: context];
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
if (modseq)
[syncCache setObject: modseq forKey: serverId];
if (modseq && [syncCache objectForKey: serverId])
[syncCache setObject: [modseq stringValue] forKey: serverId];
}
}
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
[theBuffer appendString: @"<Change>"];
@@ -511,6 +583,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// FIXME: handle errors here
if (deletesAsMoves && theFolderType == ActiveSyncMailFolder)
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context];
else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder)
{
[sogoObject prepareDelete];
[sogoObject delete];
}
else
[sogoObject delete];
}
@@ -577,36 +654,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFilterType: (NSCalendarDate *) theFilterType
inBuffer: (NSMutableString *) theBuffer
lastServerKey: (NSString **) theLastServerKey
defaultInterval: (unsigned int) theDefaultInterval
{
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
NSString *davCollectionTagToStore;
NSAutoreleasePool *pool;
NSMutableString *s;
BOOL more_available;
BOOL cleanup_needed, more_available;
int i, max;
s = [NSMutableString string];
more_available = NO;
cleanup_needed = more_available = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
// If this is a new sync operation, DateCache and SyncCache needs to be deleted
// If this is a new sync operation, DateCache and SyncCache need to be deleted
if ([theSyncKey isEqualToString: @"-1"])
{
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
}
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
{
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
// We need to cleanup this mess.
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@ syncKey: %@ cache: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login], theSyncKey, [folderMetadata objectForKey: @"SyncKey"]];
cleanup_needed = YES;
}
syncCache = [folderMetadata objectForKey: @"SyncCache"];
dateCache = [folderMetadata objectForKey: @"DateCache"];
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
theFilterType)
(cleanup_needed ||
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
theFilterType
)
{
NSArray *allKeys;
NSString *key;
@@ -622,14 +707,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
{
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
if ([syncCache objectForKey:key])
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete %@", key];
[syncCache removeObjectForKey: key];
[dateCache removeObjectForKey: key];
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
[syncCache removeObjectForKey: key];
//[dateCache removeObjectForKey: key];
softdelete_count++;
softdelete_count++;
}
else if (cleanup_needed)
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
// With this we make sure that a SoftDelete is set again on next sync.
[syncCache setObject: @"0" forKey: key];
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
// Now we are save to remove the dateCache entry.
[dateCache removeObjectForKey: key];
}
}
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
@@ -656,8 +763,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
return;
more_available = NO;
davCollectionTagToStore = [theCollection davCollectionTag];
@@ -673,7 +778,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSDictionary *component;
NSArray *allComponents;
BOOL updated;
BOOL updated, initialLoadInProgress;
int deleted, return_count;
if (theFolderType == ActiveSyncContactFolder)
@@ -683,13 +788,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else
component_name = @"vtodo";
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
initialLoadInProgress = NO;
if ([theSyncKey isEqualToString: @"-1"])
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
{
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
initialLoadInProgress = YES;
else
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
}
allComponents = [theCollection syncTokenFieldsWithProperties: nil
matchingSyncToken: theSyncKey
fromDate: theFilterType
initialLoad: initialLoadInProgress];
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
// Check for the WindowSize
max = [allComponents count];
//
// Cleanup the mess
//
if (cleanup_needed)
{
for (i = 0; i < max; i++)
{
component = [allComponents objectAtIndex: i];
deleted = [[component objectForKey: @"c_deleted"] intValue];
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
continue;
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
if (deleted)
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", uid];
// For deletes we have to recreate a cache entry to make sure the delete is sent again.
[syncCache setObject: @"0" forKey: uid];
}
else
{
if ([syncCache objectForKey: uid] && [[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: ADD %@", uid];
// Cleanup the cache to make sure the add is sent again.
[syncCache removeObjectForKey: uid];
[dateCache removeObjectForKey: uid];
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid];
// Update cache entry to make sure the change is sent again.
[syncCache setObject: @"0" forKey: uid];
}
}
}
}
return_count = 0;
for (i = 0; i < max; i++)
@@ -753,35 +921,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (theFolderType == ActiveSyncContactFolder)
componentObject = [sogoObject vCard];
else
componentObject = [sogoObject component: NO secure: NO];
//
// We do NOT synchronize NEW events that are in fact, invitations
// to events. This is due to the fact that Outlook 2013 creates
// "phantom" events in the calendar that are mapped to invitations mails.
// If we synchronize these events too, it'll interfere with the whole thing
// and prevent Outlook from properly calling MeetingResponse.
//
if (!updated && theFolderType == ActiveSyncEventFolder)
{
iCalPersonPartStat partstat;
iCalPerson *attendee;
NSString *email;
email = [[[context activeUser] allEmails] objectAtIndex: 0];
attendee = [componentObject findAttendeeWithEmail: email];
if (attendee)
{
partstat = [attendee participationStatus];
if (partstat == iCalPersonPartStatNeedsAction)
{
DESTROY(pool);
continue;
}
}
}
componentObject = [sogoObject component: NO secure: NO];
[syncCache setObject: [component objectForKey: @"c_lastmodified"] forKey: uid];
@@ -847,12 +987,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSMutableArray *allCacheObjects, *sortedBySequence;
SOGoMailObject *mailObject;
NSArray *allMessages;
NSArray *allMessages, *a;
NSString *firstUIDAdded;
int j, k, return_count;
BOOL found_in_cache;
int j, k, return_count, highestmodseq;
BOOL found_in_cache, initialLoadInProgress;
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
initialLoadInProgress = NO;
found_in_cache = NO;
firstUIDAdded = nil;
if ([theSyncKey isEqualToString: @"-1"])
{
highestmodseq = 0;
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
}
else
{
a = [theSyncKey componentsSeparatedByString: @"-"];
highestmodseq = [[a objectAtIndex: 1] intValue];
}
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
{
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
initialLoadInProgress = YES;
else
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
}
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
max = [allMessages count];
allCacheObjects = [NSMutableArray array];
@@ -860,7 +1026,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (i = 0; i < max; i++)
{
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
}
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
@@ -869,15 +1035,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
if (debugOn)
{
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
}
lastCacheObject = [sortedBySequence lastObject];
//
// Cleanup the mess
//
if (cleanup_needed)
{
NSMutableArray *sortedByUID;
int uidnextFromCache;
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
[sortedByUID sortUsingSelector: @selector(compareUID:)];
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
uidnextFromCache = [[a objectAtIndex: 0] intValue];
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
// Remove all entries from cache beginning with the first uid added by the last sync request.
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
}
RELEASE(sortedByUID);
for (j = 0; j < [allCacheObjects count]; j++)
{
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
//and we would skip the cache when generating the response.
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
}
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
}
}
}
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
if (!cleanup_needed &&
[folderMetadata objectForKey: @"MoreAvailable"] &&
lastCacheObject &&
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
{
for (j = 0; j < [allCacheObjects count]; j++)
{
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
(![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
{
// We need to continue with adds or deletes from here.
found_in_cache = YES;
j--;
break;
}
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
{
// Found out where we're at, let's continue from there...
@@ -892,12 +1125,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (found_in_cache)
k = j+1;
else
{
k = 0;
j = 0;
}
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
j = k = 0;
if (debugOn)
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
return_count = 0;
@@ -911,20 +1142,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSString *lastSequence;
more_available = YES;
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
if (!firstUIDAdded)
{
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
firstUIDAdded = [a objectAtIndex: 0];
RETAIN(firstUIDAdded);
}
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, lastSequence];
if (debugOn)
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
DESTROY(pool);
break;
}
aCacheObject = [allCacheObjects objectAtIndex: k];
// If found in cache, it's either a Change or a Delete
if (debugOn)
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
// If found in cache, it's either a Change or a Delete operation.
if ([syncCache objectForKey: [aCacheObject uid]])
{
if ([[aCacheObject sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - DELETE!"];
// Deleted
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
@@ -942,9 +1188,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mailObject = [theCollection lookupName: [aCacheObject uid]
inContext: context
acquire: 0];
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
{
if (debugOn)
[self logWithFormat: @"EAS - CHANGE!"];
[s appendString: @"<Change xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
@@ -960,6 +1209,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - ADD!"];
// Added
if (![[aCacheObject sequence] isEqual: [NSNull null]])
{
@@ -992,11 +1244,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
// Save the frist UID we add. We will use it for the synckey late.
if (!firstUIDAdded)
{
firstUIDAdded = [aCacheObject uid];
RETAIN(firstUIDAdded);
if (debugOn)
[self logWithFormat: @"EAS - first uid added %@", firstUIDAdded];
}
return_count++;
}
else
{
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
if (debugOn)
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
}
}
@@ -1005,16 +1268,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (more_available)
{
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
}
else
{
[folderMetadata removeObjectForKey: @"MoreAvailable"];
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
if (firstUIDAdded)
{
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
RELEASE(firstUIDAdded);
}
else
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
}
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
RELEASE(*theLastServerKey);
} // default:
@@ -1046,8 +1318,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inBuffer: (NSMutableString *) theBuffer
processed: (BOOL *) processed
{
id <DOMNodeList> aCommandDetails;
id <DOMElement> aCommand, element;
id <DOMNodeList> aCommandDetails;
NSAutoreleasePool *pool;
NSArray *allCommands;
int i, j;
@@ -1060,6 +1333,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (j = 0; j < [(id)aCommandDetails count]; j++)
{
pool = [[NSAutoreleasePool alloc] init];
element = [aCommandDetails objectAtIndex: j];
if ([element nodeType] == DOM_ELEMENT_NODE)
@@ -1101,6 +1375,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*processed = YES;
}
}
DESTROY(pool);
}
}
}
@@ -1113,15 +1388,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
changeDetected: (BOOL *) changeDetected
maxSyncResponseSize: (int) theMaxSyncResponseSize
{
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache;
SOGoMicrosoftActiveSyncFolderType folderType;
id collection, value;
NSMutableString *changeBuffer, *commandsBuffer;
BOOL getChanges, first_sync;
unsigned int windowSize, v, status;
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache, *folderKey;
NSMutableDictionary *folderMetadata, *folderOptions;
NSMutableArray *supportedElements, *supportedElementNames;
NSMutableString *changeBuffer, *commandsBuffer;
id collection, value;
SOGoMicrosoftActiveSyncFolderType folderType;
unsigned int windowSize, v, status, i;
BOOL getChanges, first_sync;
changeBuffer = [NSMutableString string];
commandsBuffer = [NSMutableString string];
@@ -1144,7 +1420,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[theBuffer appendString: @"</Collection>"];
return;
}
//
// First check if we have any concurrent Sync requests going on for this device.
// If we do and we are still within our maximumSyncInterval, we let our EAS
// device know to retry.
//
folderKey = [self _getNameInCache: collection withType: folderType];
folderMetadata = [self _folderMetadataForKey: folderKey];
// We check for a window size, default to 100 if not specfied or out of bounds
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
@@ -1169,13 +1453,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
first_sync = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
if ([syncKey isEqualToString: @"0"])
{
davCollectionTag = @"-1";
first_sync = YES;
*changeDetected = YES;
supportedElementNames = [[[NSMutableArray alloc] init] autorelease];
value = [theDocumentElement getElementsByTagName: @"Supported"];
if ([value count])
{
supportedElements = (id)[[value lastObject] childNodes];
if ([supportedElements count])
{
for (i = 0; i < [supportedElements count]; i++)
{
if ([[supportedElements objectAtIndex: i] nodeType] == DOM_ELEMENT_NODE)
[supportedElementNames addObject: [[supportedElements objectAtIndex: i] tagName]];
}
}
[folderMetadata setObject: supportedElementNames forKey: @"SupportedElements"];
[self _setFolderMetadata: folderMetadata forKey: folderKey];
if (debugOn)
[self logWithFormat: @"EAS - %d %@: supportedElements saved: %@", [supportedElements count], [collection nameInContainer], supportedElementNames];
}
}
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
{
@@ -1200,6 +1506,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// By default, send MIME mails. See #3146 for details.
if (!bodyPreferenceType)
bodyPreferenceType = @"4";
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
if (!mimeSupport)
mimeSupport = @"1";
}
else
{
@@ -1225,11 +1536,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
[self _setFolderMetadata: folderMetadata forKey: folderKey];
}
}
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
[context setObject: mimeSupport forKey: @"MIMESupport"];
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
//
// We process the commands from the request
@@ -1268,10 +1581,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFolderType: folderType
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
inBuffer: changeBuffer
lastServerKey: &lastServerKey];
lastServerKey: &lastServerKey
defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]];
}
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
folderMetadata = [self _folderMetadataForKey: folderKey];
// If we got any changes or if we have applied any commands
// let's regenerate our SyncKey based on the collection tag.
@@ -1430,19 +1744,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOGoSystemDefaults *defaults;
id <DOMElement> aCollection;
NSMutableString *output, *s;
NSMutableDictionary *globalMetadata;
NSNumber *syncRequestInCache, *processIdentifier;
NSString *key;
NSArray *allCollections;
NSData *d;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
BOOL changeDetected;
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
// We initialize our output buffer
output = [[NSMutableString alloc] init];
defaults = [SOGoSystemDefaults sharedSystemDefaults];
defaultInterval = [defaults maximumSyncInterval];
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
@@ -1457,12 +1776,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[output appendString: @"</Sync>"];
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
RELEASE(output);
return;
}
// Let other requests know about the collections we are dealing with.
[self _setOrUnsetSyncRequest: YES collections: allCollections];
defaults = [SOGoSystemDefaults sharedSystemDefaults];
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
defaultInterval = [defaults maximumSyncInterval];
internalInterval = [defaults internalSyncInterval];
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
@@ -1472,17 +1795,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// We check to see if our heartbeat interval falls into the supported ranges.
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
{
int limit;
// Interval is too long, inform the client.
heartbeatInterval = defaultInterval;
// Outlook doesn't like this...
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval];
// When Status = 14, the Wait interval is specified in minutes while
// defaultInterval is specifed in seconds. Adjust accordinlgy.
limit = defaultInterval/60;
if (limit < 1) limit = 1;
if (limit > 59) limit = 59;
//[output appendFormat: @"<Limit>%d</Limit>", limit];
//[output appendFormat: @"<Status>%d</Status>", 14];
}
[output appendString: @"<Collections>"];
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
// We enter our loop detection change
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
@@ -1492,25 +1818,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (j = 0; j < [allCollections count]; j++)
{
aCollection = [allCollections objectAtIndex: j];
[self processSyncCollection: aCollection
inBuffer: s
changeDetected: &changeDetected
maxSyncResponseSize: maxSyncResponseSize];
if (maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize)
// Don't return a response if another Sync is waiting.
globalMetadata = [self globalMetadataForDevice];
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
[theResponse setStatus: 503];
RELEASE(output);
return;
}
if ((maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize))
break;
}
if (changeDetected)
{
[self logWithFormat: @"Change detected, we push the content."];
[self logWithFormat: @"Change detected during Sync, we push the content."];
break;
}
else if (heartbeatInterval > 1)
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
sleep(internalInterval);
total_sleep = 0;
while (total_sleep < internalInterval)
{
// We check if we must break the current synchronization since an other Sync
// has just arrived.
syncRequestInCache = [[self globalMetadataForDevice] objectForKey: @"SyncRequest"];
if (!([syncRequest isEqualToNumber: syncRequestInCache]))
{
if (debugOn)
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
// Make sure we end the heardbeat-loop.
heartbeatInterval = internalInterval = 1;
break;
}
else
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
sleep(5);
total_sleep += 5;
}
}
}
else
{
@@ -1518,8 +1881,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response.
//
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
// otherwise send an empty response.
//
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
{
// We always return the last generated response.
+11 -1
View File
@@ -1,6 +1,6 @@
/*
Copyright (c) 2014, Inverse inc.
Copyright (c) 2014-2015, Inverse inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,15 +31,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncConstants.h"
@class NSCalendarDate;
@class NSException;
@class NSMutableDictionary;
@class NSURL;
@class NSNumber;
@interface SOGoActiveSyncDispatcher : NSObject
{
NSURL *folderTableURL;
NSDictionary *imapFolderGUIDS;
id context;
NSNumber *syncRequest;
BOOL debugOn;
}
- (NSMutableDictionary *) globalMetadataForDevice;
- (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
+360 -83
View File
@@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncDispatcher.h"
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSCalendarDate.h>
#if GNUSTEP_BASE_MINOR_VERSION >= 21
#import <Foundation/NSLocale.h>
#endif
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSURL.h>
@@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
#ifdef HAVE_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#endif
@interface SOGoActiveSyncDispatcher (Sync)
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
@@ -143,20 +153,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher
static BOOL debugOn = NO;
- (id) init
{
[super init];
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
folderTableURL = nil;
imapFolderGUIDS = nil;
syncRequest = nil;
return self;
}
- (void) dealloc
{
RELEASE(folderTableURL);
RELEASE(imapFolderGUIDS);
RELEASE(syncRequest);
[super dealloc];
}
@@ -169,16 +181,16 @@ static BOOL debugOn = NO;
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
[[o properties] removeAllObjects];
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
[[o properties] setObject: theSyncKey
forKey: @"FolderSyncKey"];
[o save];
}
- (NSMutableDictionary *) _globalMetadataForDevice
- (NSMutableDictionary *) globalMetadataForDevice
{
SOGoCacheGCSObject *o;
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
@@ -202,7 +214,7 @@ static BOOL debugOn = NO;
if (theFilter)
{
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setObjectType: ActiveSyncFolderCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
@@ -229,23 +241,27 @@ static BOOL debugOn = NO;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
SOGoUserFolder *userFolder;
NSDictionary *imapGUIDs;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
// Get the GUID of the IMAP folder
imapGUIDs = [accountFolder imapFolderGUIDs];
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
if (!imapFolderGUIDS)
{
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
// Get the GUID of the IMAP folder
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
[imapFolderGUIDS retain];
}
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
}
return theIdToTranslate;
}
//
//
//
@@ -371,7 +387,7 @@ static BOOL debugOn = NO;
[[o properties ] setObject: [[newFolder nameInContainer] substringFromIndex: 6] forKey: @"displayName"];
[o save];
nameInContainer = [[NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]] stringByEscapingURL];
nameInContainer = [NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]];
}
else
{
@@ -387,17 +403,24 @@ static BOOL debugOn = NO;
SOGoAppointmentFolders *appointmentFolders;
SOGoCacheGCSObject *o;
NSString *key;
id newFolder;
nameInContainer = nil;
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
[appointmentFolders newFolderWithName: displayName
nameInContainer: &nameInContainer];
newFolder = [appointmentFolders lookupName: nameInContainer
inContext: context
acquire: NO];
[newFolder setSynchronize: YES];
if (type == 13)
nameInContainer = [NSString stringWithFormat: @"vevent/%@", nameInContainer];
else
nameInContainer = [NSString stringWithFormat: @"vtodo/%@", nameInContainer];
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
[o setObjectType: ActiveSyncFolderCacheObject];
@@ -412,12 +435,19 @@ static BOOL debugOn = NO;
SOGoContactFolders *contactFolders;
SOGoCacheGCSObject *o;
NSString *key;
id newFolder;
nameInContainer = nil;
contactFolders = [userFolder privateContacts: @"Contacts" inContext: context];
[contactFolders newFolderWithName: displayName
nameInContainer: &nameInContainer];
newFolder = [contactFolders lookupName: nameInContainer
inContext: context
acquire: NO];
[newFolder setSynchronize: YES];
nameInContainer = [NSString stringWithFormat: @"vcard/%@", nameInContainer];
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
@@ -681,6 +711,27 @@ static BOOL debugOn = NO;
}
- (void) _flattenFolders: (NSArray *) theFolders
into: (NSMutableArray *) theTarget
parent: (NSString *) theParent
{
NSArray *o;
int i;
[theTarget addObjectsFromArray: theFolders];
for (i = 0; i < [theFolders count]; i++)
{
if (theParent)
[[theFolders objectAtIndex: i] setObject: theParent forKey: @"parent"];
o = [[theFolders objectAtIndex: i] objectForKey: @"children"];
if (o)
[self _flattenFolders: o into: theTarget parent: [[theFolders objectAtIndex: i] objectForKey: @"path"]];
}
}
//
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
@@ -691,26 +742,25 @@ static BOOL debugOn = NO;
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType;
NSDictionary *folderMetadata, *imapGUIDs;
NSArray *allFoldersMetadata, *allKeys;
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
NSMutableArray *folders, *processedFolders, *allFoldersMetadata;
NSMutableDictionary *cachedGUIDs, *metadata;
NSDictionary *folderMetadata, *imapGUIDs;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
NSMutableString *s, *commands;
SOGoUserFolder *userFolder;
NSMutableArray *folders, *processedFolders;
SoSecurityManager *sm;
SOGoCacheGCSObject *o;
NSArray *allKeys;
id currentFolder;
NSData *d;
int status, command_count, i, type, fi, count;
BOOL first_sync;
sm = [SoSecurityManager sharedSecurityManager];
metadata = [self _globalMetadataForDevice];
metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
s = [NSMutableString string];
@@ -744,7 +794,8 @@ static BOOL debugOn = NO;
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
allFoldersMetadata = [accountFolder allFoldersMetadata];
allFoldersMetadata = [NSMutableArray array];
[self _flattenFolders: [accountFolder allFoldersMetadata] into: allFoldersMetadata parent: nil];
// Get GUIDs of folder (IMAP)
// e.g. {folderINBOX = folder6b93c528176f1151c7260000aef6df92}
@@ -824,46 +875,55 @@ static BOOL debugOn = NO;
}
else
{
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar";
else
folderType = @"Contacts";
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar";
else
folderType = @"Contacts";
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
// remove the folder from device if it doesn't exists or it has not the proper permissions
if (!currentFolder ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: currentFolder
inContext: context])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// We skip personal GCS folders - we always want to synchronize these
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[[currentFolder nameInContainer] isEqualToString: @"personal"])
continue;
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
if (!currentFolder ||
![currentFolder synchronize] ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: currentFolder
inContext: context])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// Handle addition and changes
for (i = 0; i < [allFoldersMetadata count]; i++)
{
folderMetadata = [allFoldersMetadata objectAtIndex: i];
nameInCache = [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]];
// In v3, the "path" value does not have a '/' at the beginning
nameInCache = [NSString stringWithFormat: @"folder%@", [folderMetadata objectForKey: @"path"]];
// we have no guid - ignore the folder
if (![imapGUIDs objectForKey: nameInCache])
continue;
serverId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: nameInCache] substringFromIndex: 6]];
name = [folderMetadata objectForKey: @"displayName"];
// In v3, we use "name" while in v2, it was "displayName"
name = [folderMetadata objectForKey: @"name"];
// avoid duplicate folders if folder is returned by different imap namespaces
if ([processedFolders indexOfObject: serverId] == NSNotFound)
@@ -884,7 +944,7 @@ static BOOL debugOn = NO;
{
// make sure that parent of main-folders is always 0
if (type == 12)
parentId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]] substringFromIndex: 6]];
parentId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: [NSString stringWithFormat: @"folder%@", [folderMetadata objectForKey: @"parent"]]] substringFromIndex: 6]];
name = [[name pathComponents] lastObject];
}
@@ -937,7 +997,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
[o save];
command_count++;
@@ -946,17 +1008,29 @@ static BOOL debugOn = NO;
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
[folders autorelease];
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
// Inside this loop we remove all the folder without write/delete permissions
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
// the ones without write/delete permissions.
count = [folders count]-1;
for (; count >= 0; count--)
{
if ([sm validatePermission: SoPerm_DeleteObjects
onObject: [folders objectAtIndex: count]
currentFolder = [folders objectAtIndex: count];
// We skip personal GCS folders - we always want to synchronize these
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[[currentFolder nameInContainer] isEqualToString: @"personal"])
continue;
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
![currentFolder synchronize] ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: [folders objectAtIndex: count]
onObject: currentFolder
inContext: context])
{
[folders removeObjectAtIndex: count];
@@ -964,7 +1038,6 @@ static BOOL debugOn = NO;
}
count = [folders count]-1;
NSString *operation;
for (fi = 0; fi <= count ; fi++)
{
@@ -1022,7 +1095,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
}
[o save];
@@ -1045,7 +1120,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
}
[o save];
@@ -1204,7 +1281,7 @@ static BOOL debugOn = NO;
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
count = [allMessages count];
@@ -1254,7 +1331,7 @@ static BOOL debugOn = NO;
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId;
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
NSMutableString *s;
NSArray *fetchRequests;
id aFetch;
@@ -1337,7 +1414,7 @@ static BOOL debugOn = NO;
[s appendString: @"<Fetch>"];
[s appendString: @"<Status>1</Status>"];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", [fileReference stringByEscapingURL]];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", fileReference];
[s appendString: @"<Properties>"];
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
@@ -1367,6 +1444,8 @@ static BOOL debugOn = NO;
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
[context setObject: mimeSupport forKey: @"MIMESupport"];
currentCollection = [self collectionFromId: realCollectionId type: folderType];
@@ -2024,13 +2103,13 @@ static BOOL debugOn = NO;
if ([foldersWithChanges count])
{
[self logWithFormat: @"Change detected, we push the content."];
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
status = 2;
break;
}
else
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
sleep(internalInterval);
}
}
@@ -2094,6 +2173,123 @@ static BOOL debugOn = NO;
[theResponse setContent: d];
}
//
//
//
#ifdef HAVE_OPENSSL
- (unsigned int) validateCert: (NSString *) theCert
{
NSData *d;
const unsigned char *data;
X509_STORE_CTX *ctx;
X509_LOOKUP *lookup;
X509_STORE *store;
X509 *cert;
BOOL success;
size_t len;
int rc;
success = NO;
d = [theCert dataByDecodingBase64];
data = (unsigned char *)[d bytes];
len = [d length];
cert = d2i_X509(NULL, &data, len);
if (!cert)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
return 17;
}
store = X509_STORE_new();
OpenSSL_add_all_algorithms();
if (store)
{
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
if (lookup)
{
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (lookup)
{
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
ERR_clear_error();
success = YES;
}
}
}
if (!success)
{
if (store)
{
X509_STORE_free(store);
store = NULL;
}
}
ctx = X509_STORE_CTX_new();
if (!ctx)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
return 17;
}
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
X509_STORE_CTX_free(ctx);
return 17;
}
rc = X509_verify_cert(ctx);
X509_STORE_CTX_free(ctx);
X509_free(cert);
if (rc)
{
return 1;
}
else
{
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
return 17;
}
}
#else
- (unsigned int) validateCert: (NSString *) theCert
{
return 17;
}
#endif
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSMutableString *s;
NSString *cert;
NSData *d;
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
[s appendString: @"<Status>1</Status><Certificate>"];
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
[s appendString: @"</Certificate></ValidateCert>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
}
//
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
@@ -2410,39 +2606,100 @@ static BOOL debugOn = NO;
NGMimeMessageParser *parser;
NGMimeMessage *message;
NSException *error;
NSData *data;
NGMutableHashMap *map;
NGMimeMessage *messageToSend;
NGMimeMessageGenerator *generator;
NSMutableData *data;
NSData *new_from_header;
NSDictionary *identity;
NSString *fullName, *email;
const char *bytes;
int i, e, len;
BOOL found_header;
// We get the mail's data
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
// We extract the recipients
parser = [[NGMimeMessageParser alloc] init];
message = [parser parsePartFromData: data];
RELEASE(parser);
map = [NGHashMap hashMapWithDictionary: [message headers]];
identity = [[context activeUser] primaryIdentity];
fullName = [identity objectForKey: @"fullName"];
email = [identity objectForKey: @"email"];
if ([fullName length])
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding:NSUTF8StringEncoding];
else
[map setObject: email forKey: @"from"];
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
bytes = [data bytes];
len = [data length];
i = 0;
found_header = NO;
[messageToSend setBody: [message body]];
// Search for the from-header
while (i < len)
{
if (i == 0 &&
(*bytes == 'f' || *bytes == 'F') &&
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
(*(bytes+4) == ':'))
{
found_header = YES;
break;
}
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
(*(bytes+6) == ':'))
{
found_header = YES;
i = i + 2; // \r\n
bytes = bytes + 2;
break;
}
bytes++;
i++;
}
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
e = i;
while (e < len)
{
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
{
e = e + 2;
break;
}
bytes++;
e++;
}
// Update/Add the From header in the MIMEBody of the SendMail request.
// Any other way to modify the mail body would break s/mime emails.
if (found_header)
{
// Change the From header
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
else
{
// Add a From header
[data replaceBytesInRange: NSMakeRange(0, 0)
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
data = [generator generateMimeFromPart: messageToSend];
error = [self _sendMail: data
recipients: [message allRecipients]
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
@@ -2619,6 +2876,11 @@ static BOOL debugOn = NO;
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
// If there is no References: header, initialize it with In-Reply-To.
if ([mailObject inReplyTo] && ![references count])
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
if ([references count] > 0)
{
// If there are more than ten identifiers listed, we eliminate the second one.
@@ -2934,9 +3196,25 @@ static BOOL debugOn = NO;
options: NSCaseInsensitiveSearch].location == NSNotFound)
{
NSString *value;
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
#if GNUSTEP_BASE_MINOR_VERSION < 21
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
locale: nil];
#else
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
locale: [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
@"May", @"Jun", @"Jul", @"Aug",
@"Sep", @"Oct", @"Nov", @"Dec", nil],
@"NSShortMonthNameArray",
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
@"Fri", @"Sat", nil],
@"NSShortWeekDayNameArray",
nil]];
#endif
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
}
else
{
@@ -3002,7 +3280,7 @@ static BOOL debugOn = NO;
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
[theResponse setHeader: @"2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
if (debugOn && [[theResponse content] length])
if (debugOn && [[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.wbxml"] && [[theResponse content] length])
[self logWithFormat: @"EAS - response for device %@: %@", [context objectForKey: @"DeviceId"], [[[NSString alloc] initWithData: [[theResponse content] wbxml2xml] encoding: NSUTF8StringEncoding] autorelease]];
RELEASE(context);
@@ -3026,8 +3304,7 @@ static BOOL debugOn = NO;
return nil;
urlString = [[user domainDefaults] folderInfoURL];
parts = [[urlString componentsSeparatedByString: @"/"]
mutableCopy];
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
[parts autorelease];
if ([parts count] == 5)
{
+73 -24
View File
@@ -472,18 +472,46 @@ struct GlobalObjectId {
return d;
}
//
//
//
- (BOOL) _sanitinizeNeeded: (NSArray *) theParts
{
NSString *encoding, *charset;
int i;
for (i = 0; i < [theParts count]; i++)
{
encoding = [[theParts objectAtIndex: i ] objectForKey: @"encoding"];
charset = [[[theParts objectAtIndex: i ] objectForKey: @"parameterList"] objectForKey: @"charset"];
if (encoding && [encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame &&
charset && ![charset caseInsensitiveCompare: @"utf-8"] == NSOrderedSame
&& ![charset caseInsensitiveCompare: @"us-ascii"] == NSOrderedSame)
{
return YES;
}
if ([self _sanitinizeNeeded: [[theParts objectAtIndex: i ] objectForKey: @"parts"]])
return YES;
}
return NO;
}
//
//
//
- (NSData *) _preferredBodyDataUsingType: (int) theType
mimeSupport: (int) theMimeSupport
nativeType: (int *) theNativeType
{
NSString *type, *subtype, *encoding;
NSData *d;
BOOL isSMIME, sanitinizeNeeded;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
isSMIME = NO;
d = nil;
// We determine the native type
@@ -494,8 +522,14 @@ struct GlobalObjectId {
else if ([type isEqualToString: @"multipart"])
*theNativeType = 4;
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
{
*theNativeType = 4;
isSMIME = YES;
}
// We get the right part based on the preference
if (theType == 1 || theType == 2)
if ((theType == 1 || theType == 2) && !isSMIME)
{
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
{
@@ -536,12 +570,12 @@ struct GlobalObjectId {
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
}
}
else if (theType == 4)
else if (theType == 4 || isSMIME)
{
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
// We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
if (sanitinizeNeeded && !isSMIME)
d = [self _sanitizedMIMEMessage];
else
d = [self content];
@@ -656,16 +690,18 @@ struct GlobalObjectId {
{
NSData *d, *globalObjId;
NSArray *attachmentKeys;
NSMutableString *s;
uint32_t v;
NSString *p;
id value;
iCalCalendar *calendar;
NSString *p, *subtype;
NSMutableString *s;
id value;
int preferredBodyType, nativeBodyType;
int preferredBodyType, mimeSupport, nativeBodyType;
uint32_t v;
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
s = [NSMutableString string];
@@ -862,7 +898,12 @@ struct GlobalObjectId {
else
{
// MesssageClass and ContentClass
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
if ([subtype isEqualToString: @"signed"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
else if ([subtype isEqualToString: @"pkcs7-mime"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
else
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
}
@@ -876,10 +917,8 @@ struct GlobalObjectId {
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
// Body - namespace 17
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
nativeBodyType = 1;
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
if (calendar && !d)
{
@@ -981,9 +1020,12 @@ struct GlobalObjectId {
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
// Set the correct type if client requested text/html but we got text/plain
// Set the correct type if client requested text/html but we got text/plain.
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
if (preferredBodyType == 2 && nativeBodyType == 1)
[s appendString: @"<Type>1</Type>"];
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
[s appendString: @"<Type>4</Type>"];
else
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
@@ -1001,7 +1043,8 @@ struct GlobalObjectId {
// Attachments -namespace 16
attachmentKeys = [self fetchFileAttachmentKeys];
if ([attachmentKeys count])
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
{
int i;
@@ -1071,15 +1114,21 @@ struct GlobalObjectId {
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
{
id value;
NSString *reference;
reference = [[[[self mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] objectAtIndex: 0];
value = [[self mailHeaders] objectForKey: @"references"];
if ([value isKindOfClass: [NSArray class]])
reference = [[[value objectAtIndex: 0] componentsSeparatedByString: @" "] objectAtIndex: 0];
else
reference = [[value componentsSeparatedByString: @" "] objectAtIndex: 0];
if ([reference length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
else if ([self inReplyTo])
else if ([[self inReplyTo] length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self inReplyTo] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
else if ([self messageId])
else if ([[self messageId] length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self messageId] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
}
+1
View File
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@interface SOGoSyncCacheObject : NSObject
{
@public
id _uid;
id _sequence;
}
+22 -13
View File
@@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSNull.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
static Class NSNullK;
@implementation SOGoSyncCacheObject
+ (void) initialize
{
NSNullK = [NSNull class];
}
- (id) init
{
if ((self = [super init]))
@@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return self;
}
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
{
id o;
o = [[self alloc] init];
[o setUID: theUID];
[o setSequence: theSequence];
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
return [o autorelease];
}
@@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) uid
{
return _uid;
return [_uid description];
}
- (void) setUID: (id) theUID
@@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) sequence
{
return _sequence;
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
}
- (void) setSequence: (id) theSequence
@@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
{
return [[self uid] compare: [theSyncCacheObject uid]];
return [self->_uid compare: theSyncCacheObject->_uid];
}
//
@@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
{
if ([[self sequence] isEqual: [NSNull null]] &&
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
if ([self->_sequence isEqual: [NSNull null]] &&
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
return [self compareUID: theSyncCacheObject];
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedDescending;
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedAscending;
// Must check this here, to avoid comparing NSNull objects
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
return [self compareUID: theSyncCacheObject];
return [[self sequence] compare: [theSyncCacheObject sequence]];
return [self->_sequence compare: theSyncCacheObject->_sequence];
}
- (NSString *) description
@@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
@end
+1 -1
View File
@@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
return s;
+3 -1
View File
@@ -41,7 +41,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context;
- (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context;
- (void) changeParticipationStatus: (NSDictionary *) theValues
inContext: (WOContext *) context
component: (id) component;
@end
#endif
+337 -37
View File
@@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
@@ -45,11 +47,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/NSCalendarDate+NGCards.h>
#import <NGCards/NSString+NGCards.h>
#import <NGCards/iCalCalendar.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/WORequest+SOGo.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentObject.h>
#include "iCalAlarm+ActiveSync.h"
#include "iCalRecurrenceRule+ActiveSync.h"
@@ -98,10 +106,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else if ([self created])
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([self startDate])
{
if ([self isAllDay])
if ([self isAllDay] && !tz)
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -114,7 +125,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([self endDate])
{
if ([self isAllDay])
if ([self isAllDay] && !tz)
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -124,20 +135,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
if (!tz)
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
if (![self recurrenceId])
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
// Organizer and other invitations related properties
if ((organizer = [self organizer]))
{
meetingStatus = 1; // meeting and the user is the meeting organizer.
o = [organizer rfc822Email];
if ([o length])
if (![self recurrenceId] && [o length])
{
[s appendFormat: @"<Organizer_Email xmlns=\"Calendar:\">%@</Organizer_Email>", o];
@@ -226,9 +235,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
if ([[self uid] length])
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
if (![self recurrenceId] && [[self uid] length])
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [[self uid] activeSyncRepresentationInContext: context]];
// Recurrence rules
if ([self isRecurrent])
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
// Comment
o = [self comment];
//if (![self recurrenceId] && [o length])
if ([o length])
{
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
o = [o activeSyncRepresentationInContext: context];
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
{
[s appendFormat: @"<Body xmlns=\"Calendar:\">%@</Body>", o];
[s appendString: @"<BodyTruncated xmlns=\"Calendar:\">0</BodyTruncated>"];
}
else
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
}
// Sensitivity
if ([[self accessClass] isEqualToString: @"PRIVATE"])
v = 2;
@@ -251,7 +288,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"</Categories>"];
}
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
// TODO: improve this to handle more alarm types
if ([self hasAlarms])
@@ -262,36 +298,79 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendString: [alarm activeSyncRepresentationInContext: context]];
}
// Recurrence rules
// Exceptions
if ([self isRecurrent])
{
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
}
NSMutableArray *components, *exdates;
iCalEvent *current_component;
NSString *recurrence_id;
// Comment
o = [self comment];
if ([o length])
{
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
o = [o activeSyncRepresentationInContext: context];
unsigned int count, max, i;
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
components = [NSMutableArray arrayWithArray: [[self parent] events]];
max = [components count];
if (max > 1 || [self hasExceptionDates])
{
[s appendFormat: @"<Body xmlns=\"Calendar:\">%@</Body>", o];
[s appendString: @"<BodyTruncated xmlns=\"Calendar:\">0</BodyTruncated>"];
exdates = [NSMutableArray arrayWithArray: [self exceptionDates]];
[s appendString: @"<Exceptions xmlns=\"Calendar:\">"];
for (count = 1; count < max; count++)
{
current_component = [components objectAtIndex: count] ;
if ([self isAllDay])
{
recurrence_id = [NSString stringWithFormat: @"%@",
[[[current_component recurrenceId] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: -[userTimeZone secondsFromGMTForDate:
[current_component recurrenceId]]]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
else
{
recurrence_id = [NSString stringWithFormat: @"%@", [[current_component recurrenceId]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
[s appendString: @"<Exception>"];
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
[s appendFormat: @"%@", [current_component activeSyncRepresentationInContext: context]];
[s appendString: @"</Exception>"];
}
for (i = 0; i < [exdates count]; i++)
{
[s appendString: @"<Exception>"];
[s appendString: @"<Exception_Deleted>1</Exception_Deleted>"];
if ([self isAllDay])
{
recurrence_id = [NSString stringWithFormat: @"%@",
[[[[exdates objectAtIndex: i] asCalendarDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: -[userTimeZone secondsFromGMTForDate:
[[exdates objectAtIndex: i] asCalendarDate]]]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
else
{
recurrence_id = [NSString stringWithFormat: @"%@", [[[exdates objectAtIndex: i] asCalendarDate]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
[s appendString: @"</Exception>"];
}
[s appendString: @"</Exceptions>"];
}
else
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
}
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
if (![self recurrenceId])
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
return s;
}
@@ -337,13 +416,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context
{
iCalDateTime *start, *end;
iCalDateTime *start, *end; //, *oldstart;
NSCalendarDate *oldstart;
NSTimeZone *userTimeZone;
iCalTimeZone *tz;
id o;
int deltasecs;
BOOL isAllDay;
NSMutableArray *occurences;
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
if ((o = [theValues objectForKey: @"UID"]))
[self setUid: o];
@@ -356,6 +441,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
isAllDay = YES;
}
else if ([occurences count] && !([theValues objectForKey: @"AllDayEvent"]))
{
// If the occurence has no AllDay tag use it from master.
isAllDay = [[occurences objectAtIndex: 0] isAllDay];
}
//
// 0- free, 1- tentative, 2- busy and 3- out of office
@@ -422,6 +512,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
o = [o calendarDate];
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
oldstart = [start dateTime];
[start setTimeZone: tz];
if (isAllDay)
@@ -433,6 +524,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[start setDateTime: o];
}
// Calculate delta if start date has been changed.
if (oldstart)
deltasecs = [[start dateTime ] timeIntervalSinceDate: oldstart] * -1;
else
deltasecs = 0;
}
if ((o = [theValues objectForKey: @"EndTime"]))
@@ -491,6 +588,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
RELEASE(rule);
[rule takeActiveSyncValues: o inContext: context];
// Exceptions
if ((o = [theValues objectForKey: @"Exceptions"]) && [o isKindOfClass: [NSArray class]])
{
NSCalendarDate *recurrenceId, *adjustedRecurrenceId, *currentId;
NSMutableArray *exdates, *exceptionstouched;
iCalEvent *currentOccurence;
NSDictionary *exception;
unsigned int i, count, max;
[self removeAllExceptionDates];
exdates = [NSMutableArray array];
exceptionstouched = [NSMutableArray array];
for (i = 0; i < [o count]; i++)
{
exception = [o objectAtIndex: i];
if (![[exception objectForKey: @"Exception_Deleted"] intValue])
{
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
if (isAllDay)
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
// When moving the calendar entry (i.e. changing the startDate) iOS and Android sometimes send a value for
// Exception_StartTime which is not what we expect.
// With this we make sure the recurrenceId is always correct.
//
// iOS problem - If the master is a no-allday-event but the exception is, an invalid Exception_StartTime is in the request.
// e.g. it sends 20150409T040000Z instead of 20150409T060000Z;
// This might be a special case since iPhone doesn't allow an allday-exception on a non-allday-event.
if (!([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay] minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
recurrenceId = [recurrenceId hour:[[start dateTime] hourOfDay] minute:[[start dateTime] minuteOfHour]];
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
adjustedRecurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0 seconds: deltasecs];
// search for an existing occurence and update it
max = [occurences count];
currentOccurence = nil;
count = 1;
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count] ;
currentId = [currentOccurence recurrenceId];
if ([currentId compare: adjustedRecurrenceId] == NSOrderedSame)
{
[exceptionstouched addObject: adjustedRecurrenceId];
[currentOccurence takeActiveSyncValues: exception inContext: context];
break;
}
count++;
currentOccurence = nil;
}
// Create a new occurence if we found none to update.
if (!currentOccurence)
{
iCalDateTime *recid;
currentOccurence = [self mutableCopy];
[currentOccurence removeAllRecurrenceRules];
[currentOccurence removeAllExceptionRules];
[currentOccurence removeAllExceptionDates];
[[self parent] addToEvents: currentOccurence];
[currentOccurence takeActiveSyncValues: exception inContext: context];
recid = (iCalDateTime *)[currentOccurence uniqueChildWithTag: @"recurrence-id"];
if (isAllDay)
[recid setDate: adjustedRecurrenceId];
else
[recid setDateTime: adjustedRecurrenceId];
[exceptionstouched addObject: [recid dateTime]];
}
}
else if ([[exception objectForKey: @"Exception_Deleted"] intValue])
{
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
if (isAllDay)
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0
days: 0 hours: 0 minutes: 0
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
// We add only valid exception dates.
if ([self doesOccurOnDate: recurrenceId] &&
([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay]
minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
{
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
[exdates addObject: [recurrenceId dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: deltasecs]];
}
}
}
// Update exception dates in master event.
max = [exdates count];
if (max > 0)
{
for (count = 0; count < max; count++)
{
if (([exceptionstouched indexOfObject: [exdates objectAtIndex: count]] == NSNotFound))
[self addToExceptionDates: [exdates objectAtIndex: count]];
}
}
// Remove all exceptions included in the request.
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count] ;
currentId = [currentOccurence recurrenceId];
// Delete all occurences which are not touched (modified/added).
if (([exceptionstouched indexOfObject: currentId] == NSNotFound))
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
else if ([self isRecurrent])
{
// We have no exceptions in request but there is a recurrence rule.
// Remove all excpetions and exception dates.
iCalEvent *currentOccurence;
unsigned int count, max;
[self removeAllExceptionDates];
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count];
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
}
else if ([self isRecurrent])
{
// We have no recurrence rule in request.
// Remove all exceptions, exception dates and recurrence rules.
iCalEvent *currentOccurence;
unsigned int count, max;
[self removeAllRecurrenceRules];
[self removeAllExceptionRules];
[self removeAllExceptionDates];
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count];
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
// Organizer - we don't touch the value unless we're the organizer
@@ -498,7 +766,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
{
iCalPerson *person;
person = [iCalPerson elementWithTag: @"organizer"];
[person setEmail: o];
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
@@ -546,7 +814,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
// Windows phones sens sometimes an empty Attendees tag.
// We check it's an array before processing it.
if ((o = [theValues objectForKey: @"Attendees"])&& [o isKindOfClass: [NSArray class]])
if ((o = [theValues objectForKey: @"Attendees"]) && [o isKindOfClass: [NSArray class]])
{
NSMutableArray *attendees;
NSDictionary *attendee;
@@ -595,4 +863,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
- (void) changeParticipationStatus: (NSDictionary *) theValues
inContext: (WOContext *) context
component: (id) component
{
NSString *status;
id o;
// See: https://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx
//
// 0 == Free
// 1 == Tentative
// 2 == Busy
// 3 == Out of Office
// 4 == Working elsehwere
//
if ((o = [theValues objectForKey: @"BusyStatus"]))
{
// We translate >= 2 into ACCEPTED
if ([o intValue] == 0)
status = @"NEEDS-ACTION";
else if ([o intValue] >= 2)
status = @"ACCEPTED";
else
status = @"TENTATIVE";
// There's no delegate in EAS
[(SOGoAppointmentObject *) component changeParticipationStatus: status
withDelegate: nil
alarm: nil];
}
}
@end
+1 -1
View File
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Simple reccurrence rule of type "Monthly"
type = 2;
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
[[[self parent] startDate] dayOfMonth]];
(int)[[[self parent] startDate] dayOfMonth]];
}
}
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
+1 -1
View File
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
+3 -2
View File
@@ -70,8 +70,8 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
<Proxy http://127.0.0.1:20000/SOGo>
## adjust the following to your configuration
RequestHeader set "x-webobjects-server-port" "443"
RequestHeader set "x-webobjects-server-name" "yourhostname"
RequestHeader set "x-webobjects-server-url" "https://yourhostname"
# RequestHeader set "x-webobjects-server-name" "yourhostname"
# RequestHeader set "x-webobjects-server-url" "https://yourhostname"
## When using proxy-side autentication, you need to uncomment and
## adjust the following line:
@@ -90,5 +90,6 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
<IfModule rewrite_module>
RewriteEngine On
RewriteRule ^/.well-known/caldav/?$ /SOGo/dav [R=301]
RewriteRule ^/.well-known/carddav/?$ /SOGo/dav [R=301]
</IfModule>
+20539 -402
View File
File diff suppressed because it is too large. Load diff
+20 -16
View File
@@ -43,23 +43,14 @@ GNUstep
SOPE
~~~~
AngularJS
~~~~~~~~~
JavaScript libraries
~~~~~~~~~~~~~~~~~~~~
* https://angularjs.org/
* http://angular-ui.github.io/ui-router/
* https://www.airpair.com/angularjs/posts/angularjs-tutorial
Angular Material
~~~~~~~~~~~~~~~~
* https://material.angularjs.org/
Lo-Dash
~~~~~~~
* http://lodash.com/
* http://underscorejs.org/
* https://github.com/nervgh/angular-file-upload
Code Style
----------
@@ -71,6 +62,11 @@ Objective-C
apidoc -f ".*\\.m" -i UI -o Documentation/JSON-API
* When returning JSON from an object extending UIxComponent, use the following method:
- (WOResponse *) responseWithStatus: (unsigned int) status
andJSONRepresentation: (NSObject *) contentObject;
HTML
~~~~
@@ -90,6 +86,8 @@ JavaScript
* http://eslint.org/
* https://github.com/toddmotto/angularjs-styleguide
* https://github.com/angular/material/blob/master/docs/guides/CODING.md
* http://jshint.com/
* https://github.com/angular/material/tree/master/docs/guides
* We conform to https://github.com/airbnb/javascript[Airbnb coding standards]. Use https://github.com/jscs-dev/node-jscs[jscs] with the 'airbnb' preset to validate your code style:
@@ -97,7 +95,7 @@ JavaScript
* For documentation, we follow the http://usejsdoc.org/[JSDoc3] standards:
jsdoc UI/WebServerResources/js/{Appointments,Common,Contacts,Mailer}/*.js -d Documentation/JavaScript-API
jsdoc UI/WebServerResources/js/{Common,Contacts,Mailer,Preferences,Scheduler}/*.js -d Documentation/JavaScript-API
Models (facades)
^^^^^^^^^^^^^^^^
@@ -114,6 +112,12 @@ SASS/CSS
* http://componentcss.com/
* https://github.com/styleguide/css
* https://smacss.com/
* http://cssguidelin.es/
* http://sixrevisions.com/css/css-methodologies/
* https://css-tricks.com/what-a-css-code-review-might-look-like/
* http://una.im/classy-css/
A `@mixin` is like a stamp: it creates a duplicated version of the property block (optionally) with arguments provided. An `@extend` appends the element you are extending to the property block. It is your "yes, and ___" statement.
* For documentation, we follow http://sassdoc.com/[SassDoc] annotations.
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
@@ -126,14 +130,14 @@ SASS/CSS
Fonts
^^^^^
* We build a custom icons font using the online app https://icomoon.io/app/#/select[icomoon].
* http://www.carrois.com/fira-4-0/
* Material Design Iconic Font http://zavoloklom.github.io/material-design-iconic-font/icons.html
* [Mozilla Fira](http://mozilla.github.io/Fira/)
* [Material icons](https://www.google.com/design/icons/)
Version Control
---------------
* https://devcharm.com/articles/46/improve-your-git-workflow/
* https://github.com/angular/material/blob/master/docs/guides/CONTRIBUTING.md#-commit-message-format
* Each commit should cover only one thing;
* Begin the commit message with a single short (less than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description;
* When fixing a bug, commit to the devel branch as well as the maintenance branch of the latest release version (named maintenance/x.y). When a ticket is associated to the bug, add to the description a line saying *Fixes #1234*.
+34 -7
View File
@@ -1,5 +1,7 @@
Installation and Configuration Guide
====================================
:toc: left
:icons: font
////
@@ -54,7 +56,7 @@ at http://www.sogo.nu/
Architecture and Compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
image::images/architecture.png[System Architecture]
image::images/architecture.png[System Architecture, 400, 964]
Standard protocols such as CalDAV, CardDAV, GroupDAV, HTTP, IMAP and
SMTP are used to communicate with the SOGo platform or its
@@ -170,7 +172,7 @@ supported by SOGo:
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
* Ubuntu 10.04 (Lucid) to 14.04 (Trusty)
* Ubuntu 12.04 (Precise) to 14.04 (Trusty)
Make sure the required components are started automatically at boot time
and that they are running before proceeding with the SOGo configuration.
@@ -179,7 +181,7 @@ standard distribution. For example, if you are using Red Hat Enterprise
Linux 5, you have to be subscribed to the Red Hat Network before
continuing with the SOGo software installation.
This document covers the installation of SOGo under RHEL 6.
NOTE: This document covers the installation of SOGo under RHEL 6.
For installation instructions on Debian and Ubuntu, please refer
directly to the SOGo website at http://www.sogo.nu/.
@@ -207,7 +209,7 @@ content:
----
[SOGo]
name=Inverse SOGo Repository
baseurl=http://inverse.ca/downloads/SOGo/RHEL6/$basearch
baseurl=http://inverse.ca/rhel-v3/6/$basearch
gpgcheck=0
----
@@ -294,7 +296,7 @@ associated to a domain is limited to access only the users data from the
same domain. Consequently, the configuration parameters of SOGo are
defined on three levels:
image::images/preferences-hierarchy.png[Preferences Hierarchy]
image::images/preferences-hierarchy.png[Preferences Hierarchy, 400, 400]
Each level inherits the preferences of the parent level. Therefore,
domain preferences define the defaults values of the user preferences,
@@ -1125,7 +1127,7 @@ fax number, one could map it to the _facsimiletelephonenumber_ attribute
like this:
----
mapping = \{
mapping = {
  facsimiletelephonenumber = ("fax", "facsimiletelephonenumber");
};
----
@@ -1628,6 +1630,16 @@ Defaults to `Trash` when unset.
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
For example: `INBOX/Trash`.
|U |SOGoJunkFolderName
|Parameter used to set the IMAP folder name used to store junk
messages.
Defaults to `Junk` when unset.
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
For example: `INBOX/Junk`. Also see the SOGoMailJunkSettings for
more options regarding junk/not-junk actions.
|D |SOGoIMAPCASServiceName
|Parameter used to set the CAS service name (URL) of the imap service.
This is useful if SOGo is connecting to the IMAP service through a
@@ -2082,7 +2094,21 @@ Defaults to `%{FolderName} (%{UserName} <%{Email}>)` when unset.
the template named `UIxAdditionalPreferences.wox`. This template should
be put under `~sogo/GNUstep/Library/SOGo/Templates/PreferencesUI/`.
Defaults to `NO` when unset.
|D |SOGoMailJunkSettings
|Parameter used to enable email junk settings. The value is a dictionary
and the follow keys are supported: `vendor` (which must be set to "generic"
for now), `junkEmailAddress` which sets the email address to whom SOGo will
send junk mails to, `notJunkEmailAddress` which sets the email address to
whome SOGo will send non-junk mails to and `limit`, which is an integer value
and sets the maximum number of mails that will be attached to a
junk/not junk report sent by SOGo. Example: `SOGoMailJunkSettings = {
vendor = "generic"; junkEmailAddress = "spam@foo.com";
notJunkEmailAddress = "ham@foo.com"; limit = 10;
};`
|D |SOGoMailKeepDraftsAfterSend
|Parameter used to keep mails in the drafts folder once they have been
sent by SOGo. Defaults to `NO` when unset.
|=======================================================================
SOGo Configuration Summary
@@ -2111,6 +2137,7 @@ like this:
SOGoDraftsFolderName = Drafts;
SOGoSentFolderName = Sent;
SOGoTrashFolderName = Trash;
SOGoJunkFolderName = Junk;
SOGoMailingMechanism = smtp;
SOGoSMTPServer = 127.0.0.1;
SOGoUserSources = (
@@ -233,6 +233,7 @@ Installation
This section will guide you through the installation of the native
Microsoft Outlook compatibility layer SOGo offers.
////
Red Hat Enterprise Linux v6 x86_64
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -316,7 +317,7 @@ from this guide.
On Ubuntu 12.04, the Samba init scripts need to be modified to
disable the upstart check. For more details, refer to:
https://wiki.samba.org/index.php/Samba4/InitScript
////
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -387,7 +388,7 @@ Run the following commands as root: 
----
samba-tool domain provision --realm=example.com \
--domain=OPENCHANGE \
--domain=EXAMPLE \
--adminpass='%1OpenChange' \
--server-role='domain controller'
@@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
----
You might consider changing the realm and domain used, to suit your
enviroment.
environment.
You might also have to
remove `/etc/samba/smb.conf` prior running this command.
@@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
OpenChange Configuration
~~~~~~~~~~~~~~~~~~~~~~~~
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
First, create the OpenChange MySQL user:
@@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
----
////
On RHEL, make sure SELinux is disabled:
setenforce 0
////
Next, you can start Samba using the usual command:
@@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
While HTTPS is not required to access this service, it is strongly
recommended.
////
On RHEL-based distributions, the apache configuration required by these
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
`/etc/httpd/conf.d/rpcproxy.conf`.
////
For Debian-based distributions, these files can be found
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
@@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
a2enconf ocsmanager
a2enconf rpcproxy
////
On RHEL-based distributions, make sure the `LoadModule` directive is
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
////
The _reqtimeout_ apache module is known to cause problems when using the
default configuration shipped with Debian-based systems. On such
@@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
a2dismod reqtimeout
You should now restart the Apache service and make sure it will start on
boot. On RHEL-based distributions, do:
boot.
////
On RHEL-based distributions, do:
chkconfig httpd on && /etc/init.d/httpd restart
////
On Debian-based distributions, do:
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
@@ -809,11 +818,10 @@ can be ignored for now. This feature is currently not supported.
remove any data associated with the user from the SOGo server and
recreate a Microsoft Outlook profile.
To remove any data associated to a user, use
the `openchange_user_cleanup` script distributed with SOGo. The script
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
RHEL).
the `openchange_user_cleanup` script distributed with OpenChange. The script
can be found in `/usr/share/openchange/`.
To reset a user, run the script as root:
`python openchange_user_cleanup username`. See the usage output for additional options.
`openchange_user_cleanup username`. See the usage output for additional options.
* The "Out of Office Assistant" will not currently work. This feature
has not been implemented.
* Creating folders below INBOX (when not normally permitted by the IMAP
+3 -3
View File
@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 2.3.1 - July 2015</releaseinfo>
<subtitle>for version 2.3.1</subtitle>
<date>2015-07-23</date>
<releaseinfo>Version 3.0.1 - February 2016</releaseinfo>
<subtitle>for version 3.0.1</subtitle>
<date>2016-02-05</date>
<legalnotice>
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
@@ -6,13 +6,13 @@
Authors:
- Inverse inc. <info@inverse.ca>
Copyright (C) 2008-2015 Inverse inc.
Copyright (C) 2008-2016 Inverse inc.
License: GFDL 1.2 or later. http://www.gnu.org/licenses/fdl.html
////
// TODO have the build system take care of this
:release_version: 2.3.1
:release_version: 3.0.1
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
+1 -1
View File
@@ -267,7 +267,7 @@
NSObject <DOMNodeList> *list;
NSObject <DOMNode> *valueNode;
NSArray *elements;
NSString *property, *match;
NSString *property=nil, *match=nil;
list = [searchElement getElementsByTagName: @"prop"];
if ([list length])
+178 -5
View File
@@ -1,12 +1,184 @@
2.3.x (2015-MM-DD)
3.0.1 (2016-02-05)
------------------
Enhancements
- [web] improved scrolling behavior when deleting a single message (#3489)
- [web] added "Move To" option for selected messages (#3477)
- [web] updated CKEditor to version 4.5.7
- [web] updated Angular Material to 1.0.5
- [web/eas] add shared/public namespaces in the list or returned folders
Bug fixes
- [web] safeguard against mailboxes with no quota (#3468)
- [web] fixed blank calendar view when selecting "Descending Order" in the sort menu
- [web] show active user's default email address instead of system email address (#3473)
- [web] fixed display of HTML tags when viewing a message raw source (#3490)
- [web] fixed IMIP accept/decline when there is only one MIME part
- [web] improved handling of IMAP connection problem in Web interface
- [web] fixed frequency parsing of recurrence rule when saving new appointment (#3472)
- [web] added support for %p in date formatting (#3480)
- [web] make sure an email is defined before trying to use it (#3488)
- [web] handle broken messages that have no date (#3498)
- [web] fixed virtual-repeater display in Webmail when a search is performed (#3500)
- [web] fixed drag'n'drop of all-day events in multicolumn view
- [eas] correctly encode filename of attachments over EAS (#3491)
3.0.0 (2016-01-27)
------------------
New features
- complete rewrite of the JavaScript frontend using Angular and AngularMaterial
- responsive design and accessible options focused on mobile devices
- horizontal 3-pane view for a better experience on large desktop screens
- new color palette and better contrast ratio as recommended by the W3C
- improved accessibility to persons with disabilities by enabling common ARIA attributes
- use of Mozilla's Fira Sans typeface
- and many more!
2.3.7 (2016-01-25)
------------------
New features
- new junk/not junk capability with generic SMTP integration
Enhancements
- newly created folders using EAS are always sync'ed by default (#3454)
- added Croatian (hr_HR) translation - thanks to Jens Riecken
Bug fixes
- now always generate invitation updates when using EAS
- rewrote the string sanitization to be 32-bit Unicode safe
- do not try to decode non-wbxml responses for debug output (#3444)
2.3.6 (2016-01-18)
------------------
New features
- now able to sync only default mail folders when using EAS
Enhancements
- Unit testing for RTFHandler
- JUnit output for sogo-tests
Bug fixes
- don't unescape twice mail folder names (#3423)
- don't consider mobile Outlook EAS clients as DAV ones (#3431)
- we now follow 301 redirects when fetching ICS calendars
- when deleting an event using EAS, properly invoke the auto-scheduling code
- do not include failure attachments (really long filenames)
- fix encoding of email subjects with non-ASCII characters
- fix appointment notification mails using SOGoEnableDomainBasedUID configuration
- fix shifts in event times on Outlook
2.3.5 (2016-01-05)
------------------
Enhancements
- return an error to openchange if mail message delivery fails
- return the requested elements on complex requests from Outlook when downloading changes
- user sources can be loaded dynamically
- unify user sources API
- updated Russian translation (#3383)
Bug fixes
- properly compute the last week number for the year (#1010)
- share calendar, tasks and contacts folders in Outlook 2013 with editor permissions
- priorize filename in Content-Disposition against name in Content-Type to get the filename of an attachment in mail
- request all contacts when there is no filter in Contacts menu in Webmail
- personal contacts working properly on Outlook
- fixes on RTF parsing used by event/contact description and mail as RTF to read non-ASCII characters:
better parsing of font table, when using a font, switch to its character set, correct parsing of
escaped characters and Unicode character command word support for unicode characters greater than 32767
- no crash resolving recipients after reconnecting LDAP connection
- avoid creation of phantom contacts in SOGo from distribution list synced from Outlook.
- accepted & updated event names are now shown correctly in Outlook
- provide safe guards in mail and calendar to avoid exceptions while syncing
2.3.4 (2015-12-15)
------------------
New features
- initial support for EAS calendar exceptions
Enhancements
- limit the maximum width of toolbar buttons (#3381)
- updated CKEditor to version 4.5.6
Bug fixes
- JavaScript exception when printing events from calendars with no assigned color (#3203)
- EAS fix for wrong charset being used (#3392)
- EAS fix on qp-encoded subjects (#3390)
- correctly handle all-day event exceptions when the master event changes
- prevent characters in calendar component UID causing issues during import process
- avoid duplicating attendees when accepting event using a different identity over CalDAV
2.3.3a (2015-11-18)
-------------------
Bug fixes
- expanded mail folders list is not saved (#3386)
- cleanup translations
2.3.3 (2015-11-11)
------------------
New features
- initial S/MIME support for EAS (#3327)
- now possible to choose which folders to sync over EAS
Enhancements
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
- support for ghosted elements on contacts over EAS
- added Macedonian (mk_MK) translation - thanks to Miroslav Jovanovic
- added Portuguese (pt) translation - thanks to Eduardo Crispim
Bug fixes
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
- correctly handle the References header over EAS (#3365)
- make sure English is always used when generating Date headers using EAS (#3356)
- don't escape quoted strings during versit generation
- we now return all cards when we receive an empty addressbook-query REPORT
- avoid crash when replying to a mail with no recipients (#3359)
- inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271)
- prevent postal address showing on single line over EAS (#2614)
- display missing events when printing working hours only
- fix corner case making server crash when syncing hard deleted messages when clear offline items was set up (Zentyal)
- avoid infinite Outlook client loops trying to set read flag when it is already set (Zentyal)
- avoid crashing when calendar metadata is missing in the cache (Zentyal)
- fix recurrence pattern event corner case created by Mozilla Thunderbird which made server crash (Zentyal)
- fix corner case that removes attachments on sending messages from Outlook (Zentyal)
- freebusy on web interface works again in multidomain environments (Zentyal)
- fix double creation of folders in Outlook when the folder name starts with a digit (Zentyal)
- avoid crashing Outlook after setting a custom view in a calendar folder (Zentyal)
- handle emails having an attachment as their content
- fixed JavaScript syntax error in attendees editor
- fixed wrong comparison of meta vs. META tag in HTML mails
- fixed popup menu position when moved to the left (#3381)
- fixed dialog position when at the bottom of the window (#2646, #3378)
2.3.2 (2015-09-16)
------------------
Enhancements
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
- improved EAS speed during initial syncing of large mailboxes (#3293)
- updated CKEditor to version 4.5.3
Bug fixes
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
- Fixed several sync issues on environments with multiple users (Zentyal)
- Folders from other users will no longer appear on your Outlook (Zentyal)
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
- fixed several sync issues on environments with multiple users (Zentyal)
- folders from other users will no longer appear on your Outlook (Zentyal)
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
- less sync issues when setting read flag (Zentyal)
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
- support attachments from more mail clients (Zentyal)
- avoid conflicting message on saving a draft mail (Zentyal)
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
- fixed localization of calendar categories with empty profile (#3295)
- fixed options availability in contextual menu of Contacts module (#3342)
2.3.1 (2015-07-23)
------------------
@@ -19,6 +191,7 @@ Enhancements
- added create-folder subcommand to sogo-tool to create contact and calendar folders
- group mail addresses can be used as recipient in Outlook
- added 'ActiveSync' module constraints
- updated CKEditor to version 4.5.1
- added Slovenian translation - thanks to Jens Riecken
- added Chinese (Taiwan) translation
+2 -1
View File
@@ -145,7 +145,8 @@ $(DBMSGREADER_TOOL)_LIB_DIRS += \
-L../SoObjects/SOGo/SOGo.framework/sogo -lSOGo \
-L../SOPE/GDLContentStore/obj/ -lGDLContentStore \
-L../SOPE/NGCards/obj/ -lNGCards \
-lNGObjWeb
-lNGObjWeb \
$(LIBMAPI_LIBS)
TEST_TOOL_NAME += $(PLREADER_TOOL) $(DBMSGREADER_TOOL)
+2 -3
View File
@@ -24,6 +24,7 @@
#define MAPISTORECALENDARWRAPPER_H
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalTimeZone.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import "MAPIStoreObjectProxy.h"
@@ -42,7 +43,7 @@
iCalCalendar *calendar;
iCalEvent *firstEvent;
iCalEvent *event;
NSTimeZone *timeZone;
iCalTimeZone *timeZone;
SOGoUser *user;
NSString *senderEmail;
NSData *globalObjectId;
@@ -57,12 +58,10 @@
+ (id) wrapperWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo;
- (id) initWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo;
/* getters */
+49 -106
View File
@@ -37,7 +37,9 @@
#import <NGCards/iCalEventChanges.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalTrigger.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/NSString+NGCards.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>
@@ -80,7 +82,6 @@ static NSCharacterSet *hexCharacterSet = nil;
+ (id) wrapperWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo
{
MAPIStoreAppointmentWrapper *wrapper;
@@ -88,7 +89,6 @@ static NSCharacterSet *hexCharacterSet = nil;
wrapper = [[self alloc] initWithICalEvent: newEvent
andUser: newUser
andSenderEmail: newSenderEmail
inTimeZone: newTimeZone
withConnectionInfo: newConnInfo];
[wrapper autorelease];
@@ -182,10 +182,10 @@ static NSCharacterSet *hexCharacterSet = nil;
- (id) initWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo
{
NSArray *events;
iCalTimeZone *tz;
if ((self = [self init]))
{
@@ -194,9 +194,20 @@ static NSCharacterSet *hexCharacterSet = nil;
event = newEvent;
events = [calendar events];
firstEvent = [events objectAtIndex: 0];
ASSIGN (timeZone, newTimeZone);
ASSIGN (user, newUser);
ASSIGN (senderEmail, newSenderEmail);
/* If newEvent comes from the client, we set its time zone in
updateFromMAPIProperties. If it is not present, we use the
time zone of the user */
tz = (iCalTimeZone *) [calendar firstChildWithTag: @"vtimezone"];
if (!tz)
{
tz = [iCalTimeZone timeZoneForName: [[[user userDefaults] timeZone] name]];
if (!tz)
[self logWithFormat: @"no time zone could be set"];
}
ASSIGN (timeZone, tz);
[self _setupITIPContext];
}
@@ -266,7 +277,7 @@ static NSCharacterSet *hexCharacterSet = nil;
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
entryId = MAPIStoreInternalEntryId (connInfo, username);
}
else
{
@@ -367,7 +378,7 @@ static NSCharacterSet *hexCharacterSet = nil;
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
entryId = MAPIStoreInternalEntryId (connInfo, username);
}
else
{
@@ -721,22 +732,15 @@ static NSCharacterSet *hexCharacterSet = nil;
inMemCtx: (TALLOC_CTX *) memCtx
{
NSCalendarDate *dateValue;
NSInteger offset;
// if ([event isRecurrent])
// dateValue = [event firstRecurrenceStartDate];
// else
dateValue = [event startDate];
if ([event isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -749,22 +753,14 @@ static NSCharacterSet *hexCharacterSet = nil;
exceptions, where it is the normal start date for the day of the
exception. */
NSCalendarDate *dateValue;
NSInteger offset;
dateValue = [event recurrenceId];
if (!dateValue)
dateValue = [event startDate];
[dateValue setTimeZone: timeZone];
if ([event isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -772,19 +768,12 @@ static NSCharacterSet *hexCharacterSet = nil;
inMemCtx: (TALLOC_CTX *) memCtx
{
NSCalendarDate *dateValue;
NSInteger offset;
dateValue = [firstEvent startDate];
if ([firstEvent isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -804,8 +793,8 @@ static NSCharacterSet *hexCharacterSet = nil;
month: [start monthOfYear]
day: [start dayOfMonth]
hour: 0 minute: 0 second: 0
timeZone: timeZone];
[dateValue setTimeZone: utcTZ];
timeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
@@ -829,7 +818,7 @@ static NSCharacterSet *hexCharacterSet = nil;
dateValue = [event startDate];
offset = [event durationAsTimeInterval];
if ([event isAllDay])
offset -= [timeZone secondsFromGMTForDate: dateValue];
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
@@ -847,15 +836,14 @@ static NSCharacterSet *hexCharacterSet = nil;
dateValue = [event recurrenceId];
if (!dateValue)
dateValue = [event startDate];
[dateValue setTimeZone: timeZone];
offset = [firstEvent durationAsTimeInterval];
if ([firstEvent isAllDay])
offset -= [timeZone secondsFromGMTForDate: dateValue];
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -871,7 +859,7 @@ static NSCharacterSet *hexCharacterSet = nil;
dateValue = [firstEvent startDate];
offset = [firstEvent durationAsTimeInterval];
if ([event isAllDay])
offset -= [timeZone secondsFromGMTForDate: dateValue];
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
@@ -885,23 +873,14 @@ static NSCharacterSet *hexCharacterSet = nil;
{
enum mapistore_error rc;
NSCalendarDate *dateValue;
NSInteger offset;
iCalRecurrenceRule *rrule;
if ([event isRecurrent])
{
rrule = [[event recurrenceRules] objectAtIndex: 0];
dateValue = [rrule untilDate];
if (dateValue)
{
if ([event isAllDay])
offset = -[timeZone secondsFromGMTForDate: dateValue];
else
offset = 0;
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
if (dateValue && [event isAllDay])
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
else
dateValue = [NSCalendarDate dateWithYear: 4500 month: 8 day: 31
hour: 23 minute: 59 second: 00
@@ -932,7 +911,7 @@ static NSCharacterSet *hexCharacterSet = nil;
if (contactInfos)
{
username = [contactInfos objectForKey: @"sAMAccountName"];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
entryId = MAPIStoreInternalEntryId (connInfo, username);
}
else
entryId = MAPIStoreExternalEntryId (cn, email);
@@ -1338,21 +1317,14 @@ static NSCharacterSet *hexCharacterSet = nil;
{
enum mapistore_error rc;
NSCalendarDate *dateValue;
NSInteger offset;
dateValue = [event recurrenceId];
if (dateValue)
{
rc = MAPISTORE_SUCCESS;
if ([event isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
}
else
@@ -1377,7 +1349,6 @@ static NSCharacterSet *hexCharacterSet = nil;
iCalEventChanges *changes;
NSArray *changedProperties;
NSCalendarDate *dateValue;
NSInteger offset;
changes = [iCalEventChanges changesFromEvent: event toEvent: exceptionEvent];
@@ -1385,28 +1356,17 @@ static NSCharacterSet *hexCharacterSet = nil;
memset (extendedException, 0, sizeof (struct ExtendedException));
extendedException->ChangeHighlight.Size = sizeof (uint32_t);
dateValue = [exceptionEvent startDate];
offset = [timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
dateValue = [timeZone computedDateForDate: [exceptionEvent startDate]];
exceptionInfo->StartDateTime = [dateValue asMinutesSince1601];
extendedException->ChangeHighlight.Value = BIT_CH_START;
extendedException->StartDateTime = exceptionInfo->StartDateTime;
dateValue = [exceptionEvent endDate];
offset = [timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
dateValue = [timeZone computedDateForDate: [exceptionEvent endDate]];
exceptionInfo->EndDateTime = [dateValue asMinutesSince1601];
extendedException->ChangeHighlight.Value |= BIT_CH_END;
extendedException->EndDateTime = exceptionInfo->EndDateTime;
dateValue = [[exceptionEvent recurrenceId]
dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
dateValue = [timeZone computedDateForDate: [exceptionEvent recurrenceId]];
exceptionInfo->OriginalStartDate = [dateValue asMinutesSince1601];
extendedException->OriginalStartDate = exceptionInfo->OriginalStartDate;
@@ -1464,7 +1424,6 @@ static NSCharacterSet *hexCharacterSet = nil;
arp = talloc_zero (NULL, struct AppointmentRecurrencePattern);
[rule fillRecurrencePattern: &arp->RecurrencePattern
withEvent: event
inTimeZone: timeZone
inMemCtx: arp];
arp->ReaderVersion2 = 0x00003006;
arp->WriterVersion2 = 0x00003008; /* 0x3008 for compatibility with
@@ -1475,7 +1434,7 @@ static NSCharacterSet *hexCharacterSet = nil;
fields are relative to midnight of those days ([MS-OXOCAL] 2.2.1.44.5),
so no time zone adjustment is needed */
if (![event isAllDay])
[firstStartDate setTimeZone: timeZone];
firstStartDate = [timeZone computedDateForDate: firstStartDate];
startMinutes = ([firstStartDate hourOfDay] * 60
+ [firstStartDate minuteOfHour]);
arp->StartTimeOffset = startMinutes;
@@ -1701,15 +1660,16 @@ ReservedBlockEE2Size: 00 00 00 00
fromDate: (NSCalendarDate *) instanceDate;
{
uint16_t year;
NSCalendarDate *dateValue;
if (instanceDate)
{
[instanceDate setTimeZone: timeZone];
year = [instanceDate yearOfCommonEra];
dateValue = [timeZone computedDateForDate: instanceDate];
year = [dateValue yearOfCommonEra];
newGlobalId->YH = year >> 8;
newGlobalId->YL = year & 0xff;
newGlobalId->Month = [instanceDate monthOfYear];
newGlobalId->D = [instanceDate dayOfMonth];
newGlobalId->Month = [dateValue monthOfYear];
newGlobalId->D = [dateValue dayOfMonth];
}
}
@@ -1974,7 +1934,6 @@ ReservedBlockEE2Size: 00 00 00 00
if (alarm)
{
alarmDate = [alarm nextAlarmDate];
[alarmDate setTimeZone: utcTZ];
*data = [alarmDate asFileTimeInMemCtx: memCtx];
}
else
@@ -2036,8 +1995,7 @@ ReservedBlockEE2Size: 00 00 00 00
enum mapistore_error rc;
NSString *tzid;
tzid = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"]
value: 0 ofAttribute: @"tzid"];
tzid = [timeZone tzId];
if ([tzid length] > 0)
{
*data = [tzid asUnicodeInMemCtx: memCtx];
@@ -2053,16 +2011,9 @@ ReservedBlockEE2Size: 00 00 00 00
inMemCtx: (TALLOC_CTX *) memCtx
{
enum mapistore_error rc;
iCalTimeZone *icalTZ;
icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone];
if (icalTZ)
{
*data = [icalTZ asTimeZoneStructInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
else
rc = MAPISTORE_ERR_NOT_FOUND;
*data = [timeZone asTimeZoneStructInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
return rc;
}
@@ -2071,24 +2022,16 @@ ReservedBlockEE2Size: 00 00 00 00
inMemCtx: (TALLOC_CTX *) memCtx
{
enum mapistore_error rc;
iCalTimeZone *icalTZ;
/* [MS-OXOCAL] 3.1.5.5.1: This property is used in floating (all-day) events,
specified in floating time, to convert the start date from UTC to the user's
time zone */
if ([event isAllDay])
icalTZ = [iCalTimeZone timeZoneForName: [timeZone timeZoneName]];
else if ([event isRecurrent])
icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone];
else
icalTZ = nil;
if (icalTZ)
if ([event isAllDay] | [event isRecurrent])
{
/* [MS-OXOCAL] 2.2.1.42: This property can only have the E flag set in the
TimeZoneDefinition struct */
*data = [icalTZ asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG
inMemCtx: memCtx];
*data = [timeZone asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG
inMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
else
+3
View File
@@ -46,6 +46,9 @@
forURL: (NSURL *) server
forceRenew: (BOOL) renew;
- (NSString *) passwordInContext: (WOContext *) context;
@end
#endif /* MAPISTOREAUTHENTICATOR_H */
+4
View File
@@ -72,4 +72,8 @@
return imapPassword;
}
- (NSString *) passwordInContext: (WOContext *) context
{
return password;
}
@end
@@ -58,7 +58,6 @@
wrapperWithICalEvent: [newContainer event]
andUser: [userContext sogoUser]
andSenderEmail: nil
inTimeZone: [userContext timeZone]
withConnectionInfo: [context connectionInfo]];
[self addProxy: appointmentWrapper];
}
+14 -3
View File
@@ -77,6 +77,17 @@
- (NSArray *) rolesForExchangeRights: (uint32_t) rights
{
/* Limitations
Following rights are not supported by SOGo specifically:
- DeleteOwned : Delete only own objects
- EditOwned : Edit only own objects
- CreateSubfolders: No calendar subfolders
- FolderOwner: No sharing folder ownership?
- FolderContact: No support to store this information
- FolderVisible: It is inferred by other rights when extracting
*/
NSMutableArray *roles;
roles = [NSMutableArray arrayWithCapacity: 6];
@@ -117,11 +128,11 @@
if ([roles containsObject: SOGoRole_ObjectCreator])
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoCalendarRole_PublicModifier]
&& [roles containsObject: SOGoCalendarRole_PrivateModifier]
&& [roles containsObject: SOGoCalendarRole_ConfidentialModifier])
rights |= RightsReadItems | RightsEditAll;
rights |= RightsReadItems | RightsEditAll | RightsEditOwn | RightsFreeBusySimple | RightsFreeBusyDetailed;
else if ([roles containsObject: SOGoCalendarRole_PublicViewer]
&& [roles containsObject: SOGoCalendarRole_PrivateViewer]
&& [roles containsObject: SOGoCalendarRole_ConfidentialViewer])
@@ -135,7 +146,7 @@
if ([roles containsObject: SOGoCalendarRole_ConfidentialDAndTViewer])
rights |= RightsFreeBusyDetailed;
if (rights != 0)
if ((rights & RightsReadItems) != 0 || (rights & RightsCreateItems) != 0 || (rights & RightsDeleteAll) != 0)
rights |= RoleNone; /* actually "folder visible" */
// [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights];
+1 -2
View File
@@ -197,7 +197,6 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
= [MAPIStoreAppointmentWrapper wrapperWithICalEvent: masterEvent
andUser: [userContext sogoUser]
andSenderEmail: nil
inTimeZone: [userContext timeZone]
withConnectionInfo: [context connectionInfo]];
[self addProxy: appointmentWrapper];
}
@@ -671,7 +670,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
return newAttachment;
}
- (int) setReadFlag: (uint8_t) flag
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
return MAPISTORE_SUCCESS;
}
+2 -2
View File
@@ -96,9 +96,9 @@
if ([roles containsObject: SOGoRole_ObjectCreator])
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoRole_ObjectEditor])
rights |= RightsEditAll;
rights |= RightsEditAll | RightsEditOwn;
if ([roles containsObject: SOGoRole_ObjectViewer])
rights |= RightsReadItems;
if (rights != 0)
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -102,7 +102,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
{
value = get_SPropValue_SRow (aRow, PidTagDisplayName_string8);
if (value)
folderName = [NSString stringWithUTF8String: value->value.lpszA];
folderName = [NSString stringWithUTF8String: (const char *) value->value.lpszA];
else
folderName = nil;
}
@@ -321,9 +321,9 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
if ([roles containsObject: MAPIStoreRightDeleteOwn])
rights |= RightsDeleteOwn;
if ([roles containsObject: MAPIStoreRightEditAll])
rights |= RightsEditAll;
rights |= RightsEditAll | RightsEditOwn;
if ([roles containsObject: MAPIStoreRightDeleteAll])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: MAPIStoreRightCreateSubfolders])
rights |= RightsCreateSubfolders;
if ([roles containsObject: MAPIStoreRightFolderOwner])
+228 -15
View File
@@ -22,6 +22,7 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@@ -34,6 +35,7 @@
#import "MAPIStoreDBFolder.h"
#import "MAPIStoreDBMessage.h"
#import "MAPIStoreTypes.h"
#import "NSData+MAPIStore.h"
#import "NSObject+MAPIStore.h"
#import "NSString+MAPIStore.h"
@@ -43,16 +45,28 @@
@implementation MAPIStoreDBMessage
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
+ (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
{
struct SPropTagArray *properties;
NSUInteger count;
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
0x683f0102, 0x68410003, 0x68420102,
0x68450102, 0x68460003,
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID
0x7006001F, 0x70030003, 0x70070003, 0x68330048 };
enum MAPITAGS faiProperties[] = {
0x68330048, /* PR_VIEW_CLSID */
0x68350102, /* PR_VIEW_STATE */
0x683c0102,
0x683d0040,
0x683e0102,
0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */
0x68410003,
0x68420102,
0x68450102,
0x68460003,
0x7006001F, /* PR_VD_NAME_W */
0x70030003, /* PR_VD_FLAGS */
0x70070003 /* PR_VD_VERSION */
};
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
properties = talloc_zero (memCtx, struct SPropTagArray);
@@ -74,6 +88,13 @@
return MAPISTORE_SUCCESS;
}
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
{
return [[self class] getAvailableProperties: propertiesP
inMemCtx: memCtx];
}
- (id) initWithSOGoObject: (id) newSOGoObject
inContainer: (MAPIStoreObject *) newContainer
{
@@ -88,22 +109,173 @@
return self;
}
- (NSString *) description
{
id key, value;
NSEnumerator *propEnumerator;
NSMutableString *description;
description = [NSMutableString stringWithFormat: @"%@ %@. Properties: {", NSStringFromClass ([self class]),
[self url]];
propEnumerator = [properties keyEnumerator];
while ((key = [propEnumerator nextObject]))
{
uint32_t proptag = 0;
if ([key isKindOfClass: [NSString class]] && [(NSString *)key intValue] > 0)
proptag = [(NSString *)key intValue];
else if ([key isKindOfClass: [NSNumber class]])
proptag = [key unsignedLongValue];
if (proptag > 0)
{
const char *propTagName = get_proptag_name ([key unsignedLongValue]);
NSString *propName;
if (propTagName)
propName = [NSString stringWithCString: propTagName
encoding: NSUTF8StringEncoding];
else
propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]];
[description appendFormat: @"'%@': ", propName];
}
else
[description appendFormat: @"'%@': ", key];
value = [properties objectForKey: key];
[description appendFormat: @"%@ (%@), ", value, NSStringFromClass ([value class])];
}
[description appendString: @"}\n"];
return description;
}
- (uint64_t) objectVersion
{
NSNumber *versionNbr;
/* Return the global counter from CN structure.
See [MS-OXCFXICS] Section 2.2.2.1 */
NSNumber *versionNbr, *cn;
uint64_t objectVersion;
[(SOGoMAPIDBMessage *) sogoObject reloadIfNeeded];
versionNbr = [properties objectForKey: @"version"];
versionNbr = [properties objectForKey: @"version_number"];
if (versionNbr)
objectVersion = (([versionNbr unsignedLongLongValue] >> 16)
& 0x0000ffffffffffffLL);
objectVersion = exchange_globcnt ([versionNbr unsignedLongLongValue]);
else
objectVersion = ULLONG_MAX;
{
/* Old version which stored the CN structure not useful for searching */
cn = [properties objectForKey: @"version"];
if (cn)
objectVersion = (([cn unsignedLongLongValue] >> 16)
& 0x0000ffffffffffffLL);
else
objectVersion = ULLONG_MAX;
}
return objectVersion;
}
- (void) _updatePredecessorChangeList
{
BOOL updated;
enum mapistore_error rc;
NSData *currentChangeList, *changeKey;
NSMutableArray *changeKeys;
NSMutableData *newChangeList;
NSUInteger count, len;
struct SizedXid *changes;
struct SPropValue property;
struct SRow aRow;
struct XID *currentChangeKey;
TALLOC_CTX *localMemCtx;
uint32_t nChanges;
localMemCtx = talloc_new (NULL);
if (!localMemCtx)
{
[self errorWithFormat: @"No more memory"];
return;
}
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
if (!currentChangeList)
{
/* Create a new PredecessorChangeList */
len = [changeKey length];
newChangeList = [NSMutableData dataWithCapacity: len + 1];
[newChangeList appendUInt8: len];
[newChangeList appendData: changeKey];
}
else
{
/* Update current predecessor change list with new change key */
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
with: &nChanges];
updated = NO;
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
for (count = 0; count < nChanges && !updated; count++)
{
if (GUID_equal(&changes[count].XID.NameSpaceGuid, &currentChangeKey->NameSpaceGuid))
{
NSData *globCnt, *oldGlobCnt;
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
{
if ([globCnt length] != [oldGlobCnt length])
{
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
abort();
}
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
updated = YES;
}
}
}
/* Serialise it */
changeKeys = [NSMutableArray array];
if (!updated)
[changeKeys addObject: changeKey];
for (count = 0; count < nChanges; count++)
{
changeKey = [NSData dataWithXID: &changes[count].XID];
[changeKeys addObject: changeKey];
}
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
newChangeList = [NSMutableData data];
len = [changeKeys count];
for (count = 0; count < len; count++)
{
changeKey = [changeKeys objectAtIndex: count];
[newChangeList appendUInt8: [changeKey length]];
[newChangeList appendData: changeKey];
}
}
if ([newChangeList length] > 0)
{
property.ulPropTag = PidTagPredecessorChangeList;
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
aRow.cValues = 1;
aRow.lpProps = &property;
rc = [self addPropertiesFromRow: &aRow];
if (rc != MAPISTORE_SUCCESS)
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
}
talloc_free (localMemCtx);
}
//
// FIXME: how this can happen?
//
@@ -163,10 +335,19 @@
[properties setObject: attachmentParts forKey: @"attachments"];
newVersion = [[self context] getNewChangeNumber];
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
forKey: @"version"];
newVersion = exchange_globcnt ((newVersion >> 16) & 0x0000ffffffffffffLL);
[self logWithFormat: @"%d props in dict", [properties count]];
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
forKey: @"version_number"];
/* Remove old version */
[properties removeObjectForKey: @"version"];
/* Update PredecessorChangeList accordingly */
[self _updatePredecessorChangeList];
// [self logWithFormat: @"Saving %@", [self description]];
// [self logWithFormat: @"%d props in dict", [properties count]];
[sogoObject save];
}
@@ -209,4 +390,36 @@
return [sogoObject lastModified];
}
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
/* Modify PidTagMessageFlags from SetMessageReadFlag and
SyncImportReadStateChanges ROPs */
NSNumber *flags;
uint32_t newFlag;
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
if (flags)
{
newFlag = [flags unsignedLongValue];
if (flag & SUPPRESS_RECEIPT)
newFlag |= MSGFLAG_READ;
if (flag & CLEAR_RN_PENDING)
newFlag &= ~MSGFLAG_RN_PENDING;
if (flag & CLEAR_READ_FLAG)
newFlag &= ~MSGFLAG_READ;
if (flag & CLEAR_NRN_PENDING)
newFlag &= ~MSGFLAG_NRN_PENDING;
}
else
{
newFlag = MSGFLAG_READ;
if (flag & CLEAR_READ_FLAG)
newFlag = 0x0;
}
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
return MAPISTORE_SUCCESS;
}
@end
+5 -4
View File
@@ -61,14 +61,15 @@ static Class MAPIStoreDBMessageK = Nil;
if ((uint32_t) res->ulPropTag == PidTagChangeNumber)
{
SEL operator;
value = NSObjectFromMAPISPropValue (&res->lpProp);
cVersion = exchange_globcnt (([value unsignedLongLongValue] >> 16)
& 0x0000ffffffffffffLL);
version = [NSNumber numberWithUnsignedLongLong: cVersion];
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
[self logWithFormat: @" version: %.16lx", cVersion];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"version"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
operator = [self operatorFromRestrictionOperator: res->relop];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"version_number"
operatorSelector: operator
value: version];
[*qualifier autorelease];
rc = MAPIRestrictionStateNeedsEval;
+1
View File
@@ -121,6 +121,7 @@
fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) want_copy
inMemCtx: (TALLOC_CTX *) memCtx;
+22 -11
View File
@@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
fromFolder: (MAPIStoreFolder *) sourceFolder
withMID: (uint64_t) targetMid
andChangeKey: (struct Binary_r *) targetChangeKey
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx
{
@@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
if (targetChangeKey)
if (targetPredecessorChangeList)
{
property.ulPropTag = PidTagChangeKey;
property.value.bin = *targetChangeKey;
property.ulPropTag = PidTagPredecessorChangeList;
property.value.bin = *targetPredecessorChangeList;
aRow.cValues = 1;
aRow.lpProps = &property;
rc = [destMsg addPropertiesFromRow: &aRow];
if (rc != MAPISTORE_SUCCESS)
goto end;
{
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
goto end;
}
}
[destMsg save: memCtx];
if (!wantCopy)
@@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx
{
@@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
NSString *oldMessageURL;
MAPIStoreMapping *mapping;
SOGoUser *ownerUser;
struct Binary_r *targetChangeKey;
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
//TALLOC_CTX *memCtx;
//memCtx = talloc_zero (NULL, TALLOC_CTX);
@@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
if (oldMessageURL)
{
[oldMessageURLs addObject: oldMessageURL];
if (targetChangeKeys)
targetChangeKey = targetChangeKeys[count];
if (targetChangeKeys && targetPredecessorChangeList)
{
targetChangeKey = targetChangeKeys[count];
targetPredecessorChangeList = targetPredecessorChangeLists[count];
}
else
targetChangeKey = NULL;
{
targetChangeKey = NULL;
targetPredecessorChangeList = NULL;
}
rc = [self _moveCopyMessageWithMID: srcMids[count]
fromFolder: sourceFolder
withMID: targetMids[count]
andChangeKey: targetChangeKey
andPredecessorChangeList: targetPredecessorChangeList
wantCopy: wantCopy
inMemCtx: memCtx];
}
@@ -1537,7 +1549,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
struct Binary_r bin32;
struct AddressBookEntryId *entryId;
NSString *username;
struct ldb_context *samCtx;
if (bin && bin->cb)
{
@@ -1547,8 +1558,8 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
entryId = get_AddressBookEntryId (NULL, &bin32);
if (entryId)
{
samCtx = [[self context] connectionInfo]->sam_ctx;
username = MAPIStoreSamDBUserAttribute (samCtx, @"legacyExchangeDN",
username = MAPIStoreSamDBUserAttribute ([[self context] connectionInfo],
@"legacyExchangeDN",
[NSString stringWithUTF8String: entryId->X500DN],
@"sAMAccountName");
}
+3 -1
View File
@@ -41,8 +41,10 @@
/* synchronisation */
- (BOOL) synchroniseCache;
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) newChangeKey;
withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl;
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
+197 -21
View File
@@ -75,6 +75,7 @@ static Class NSNumberK;
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
inContainer: dbFolder]);
[versionsMessage setObjectType: MAPIInternalCacheObject];
[versionsMessage reloadIfNeeded];
}
- (void) dealloc
@@ -261,7 +262,6 @@ static Class NSNumberK;
*/
- (void) _setChangeKey: (NSData *) changeKey
forMessageEntry: (NSMutableDictionary *) messageEntry
inChangeListOnly: (BOOL) inChangeListOnly
{
struct XID *xid;
NSString *guid;
@@ -270,19 +270,15 @@ static Class NSNumberK;
NSMutableDictionary *changeList;
xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->GUID];
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid);
if (!inChangeListOnly)
{
/* 1. set change key association */
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
guid, @"GUID",
globCnt, @"LocalId",
nil];
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
}
/* 1. set change key association */
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
globCnt, @"LocalId",
nil];
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
/* 2. append/update predecessor change list */
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
@@ -296,6 +292,77 @@ static Class NSNumberK;
[changeList setObject: globCnt forKey: guid];
}
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
forMessageEntry: (NSMutableDictionary *) messageEntry
withOldChangeKey: (NSData *) oldChangeKey
{
NSData *globCnt, *oldGlobCnt;
NSDictionary *changeKeyDict;
NSString *guid;
NSMutableDictionary *changeList;
struct SizedXid *sizedXIDList;
struct XID xid, *givenChangeKey;
TALLOC_CTX *localMemCtx;
uint32_t i, length;
localMemCtx = talloc_new (NULL);
if (!localMemCtx)
{
[self errorWithFormat: @"No more memory"];
return;
}
if (predecessorChangeList)
{
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
if (!changeList)
{
changeList = [NSMutableDictionary new];
[messageEntry setObject: changeList
forKey: @"PredecessorChangeList"];
[changeList release];
}
if (sizedXIDList) {
for (i = 0; i < length; i++)
{
xid = sizedXIDList[i].XID;
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
oldGlobCnt = [changeList objectForKey: guid];
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
[changeList setObject: globCnt forKey: guid];
}
}
}
if (oldChangeKey)
{
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
if (givenChangeKey) {
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
if (!changeKeyDict ||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
{
/* The given change key is greater than current one stored in
metadata or it does not exist */
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
globCnt, @"LocalId",
nil]
forKey: @"ChangeKey"];
}
}
}
talloc_free (localMemCtx);
}
- (EOQualifier *) componentQualifier
{
if (!componentQualifier)
@@ -465,8 +532,7 @@ static Class NSNumberK;
// A GLOBCNT structure is a 6-byte global namespace counter,
// we strip the first 2 bytes. The first two bytes is the ReplicaId
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
[self _setChangeKey: changeKey forMessageEntry: messageEntry
inChangeListOnly: NO];
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
}
now = [NSCalendarDate date];
@@ -482,13 +548,123 @@ static Class NSNumberK;
return rc;
}
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer
{
/* Try to synchronise old messages in versions.plist cache using an
specific c_name. It returns a boolean indicating if the
synchronisation was carried out succesfully.
It should be used as last resort, keeping synchroniseCache as the
main sync entry point. */
uint64_t changeNumber;
NSString *changeNumberStr;
NSData *changeKey;
NSNumber *cLastModified, *cDeleted, *cVersion;
EOFetchSpecification *fs;
EOQualifier *searchQualifier, *fetchQualifier;
NSArray *fetchResults;
NSDictionary *result;
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
GCSFolder *ocsFolder;
static NSArray *fields;
[versionsMessage reloadIfNeeded];
currentProperties = [versionsMessage properties];
messages = [currentProperties objectForKey: @"Messages"];
if (!messages)
{
messages = [NSMutableDictionary new];
[currentProperties setObject: messages forKey: @"Messages"];
[messages release];
}
messageEntry = [messages objectForKey: nameInContainer];
if (!messageEntry)
{
/* Fetch the message by its name */
if (!fields)
fields = [[NSArray alloc]
initWithObjects: @"c_name", @"c_version", @"c_lastmodified",
@"c_deleted", nil];
searchQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_name"
operatorSelector: EOQualifierOperatorEqual
value: nameInContainer];
fetchQualifier = [[EOAndQualifier alloc]
initWithQualifiers: searchQualifier,
[self contentComponentQualifier],
nil];
[fetchQualifier autorelease];
[searchQualifier release];
ocsFolder = [sogoObject ocsFolder];
fs = [EOFetchSpecification
fetchSpecificationWithEntityName: [ocsFolder folderName]
qualifier: fetchQualifier
sortOrderings: nil];
fetchResults = [ocsFolder fetchFields: fields
fetchSpecification: fs
ignoreDeleted: NO];
if ([fetchResults count] == 1)
{
result = [fetchResults objectAtIndex: 0];
cLastModified = [result objectForKey: @"c_lastmodified"];
cDeleted = [result objectForKey: @"c_deleted"];
if ([cDeleted isKindOfClass: NSNumberK] && [cDeleted intValue])
cVersion = [NSNumber numberWithInt: -1];
else
cVersion = [result objectForKey: @"c_version"];
changeNumber = [[self context] getNewChangeNumber];
changeNumberStr = [NSString stringWithUnsignedLongLong: changeNumber];
/* Create new message entry in Messages dict */
messageEntry = [NSMutableDictionary new];
[messages setObject: messageEntry forKey: nameInContainer];
[messageEntry release];
/* Store cLastModified, cVersion and the change number */
[messageEntry setObject: cLastModified forKey: @"c_lastmodified"];
[messageEntry setObject: cVersion forKey: @"c_version"];
[messageEntry setObject: changeNumberStr forKey: @"version"];
/* Store the change key */
changeKey = [self getReplicaKeyFromGlobCnt: changeNumber >> 16];
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
/* Store the changeNumber -> cLastModified mapping */
mapping = [currentProperties objectForKey: @"VersionMapping"];
if (!mapping)
{
mapping = [NSMutableDictionary new];
[currentProperties setObject: mapping forKey: @"VersionMapping"];
[mapping release];
}
[mapping setObject: cLastModified forKey: changeNumberStr];
/* Save the message */
[versionsMessage save];
return YES;
}
else
return NO;
}
/* If message entry exists, then synchroniseCache did its job */
return YES;
}
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) newChangeKey
withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl
{
NSMutableDictionary *messages, *messageEntry;
[self synchroniseCache];
if (newChangeKey)
if (oldChangeKey || pcl)
{
messages = [[versionsMessage properties] objectForKey: @"Messages"];
messageEntry = [messages objectForKey: messageKey];
@@ -496,8 +672,8 @@ static Class NSNumberK;
[NSException raise: @"MAPIStoreIOException"
format: @"no version record found for message '%@'",
messageKey];
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
inChangeListOnly: YES];
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
withOldChangeKey: oldChangeKey];
[versionsMessage save];
}
}
@@ -505,12 +681,12 @@ static Class NSNumberK;
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber
{
NSDictionary *mapping;
NSNumber *modseq;
NSNumber *lastModified;
mapping = [[versionsMessage properties] objectForKey: @"VersionMapping"];
modseq = [mapping objectForKey: changeNumber];
lastModified = [mapping objectForKey: changeNumber];
return modseq;
return lastModified;
}
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey
+39 -16
View File
@@ -140,9 +140,16 @@
[parentFolder synchroniseCache];
changeKey = [parentFolder changeKeyForMessageWithKey: nameInContainer];
}
if (!changeKey)
abort ();
*data = [changeKey asBinaryInMemCtx: memCtx];
if (changeKey)
*data = [changeKey asBinaryInMemCtx: memCtx];
else
{
[self warnWithFormat: @"No change key for %@ in folder %@",
nameInContainer,
[parentFolder url]
];
rc = MAPISTORE_ERR_NOT_FOUND;
}
}
return rc;
@@ -180,7 +187,8 @@
{
uint64_t version = ULLONG_MAX;
NSString *changeNumber;
BOOL synced;
if (!isNew)
{
changeNumber = [(MAPIStoreGCSFolder *) container
@@ -189,16 +197,28 @@
{
[self warnWithFormat: @"attempting to get change number"
@" by synchronising folder..."];
[(MAPIStoreGCSFolder *) container synchroniseCache];
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
if (changeNumber)
[self logWithFormat: @"got one"];
else
synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
if (synced)
{
[self errorWithFormat: @"still nothing. We crash!"];
abort();
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
}
if (!changeNumber)
{
[self warnWithFormat: @"attempting to get change number"
@" by synchronising this specific message..."];
synced = [(MAPIStoreGCSFolder *) container
synchroniseCacheFor: [self nameInContainer]];
if (synced)
{
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
}
if (!changeNumber)
{
[self errorWithFormat: @"still nothing. We crash!"];
abort();
}
}
}
version = [changeNumber unsignedLongLongValue] >> 16;
@@ -209,13 +229,16 @@
- (void) updateVersions
{
NSData *newChangeKey;
/* Update ChangeKey and PredecessorChangeList on message's save */
NSData *newChangeKey, *predecessorChangeList;
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
[(MAPIStoreGCSFolder *) container
updateVersionsForMessageWithKey: [self nameInContainer]
withChangeKey: newChangeKey];
updateVersionsForMessageWithKey: [self nameInContainer]
withChangeKey: newChangeKey
andPredecessorChangeList: predecessorChangeList];
}
@end
+14 -4
View File
@@ -27,6 +27,7 @@
#import <Foundation/NSString.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.h>
#import <EOControl/EOFetchSpecification.h>
#import <EOControl/EOQualifier.h>
@@ -38,7 +39,6 @@
#import "MAPIStoreTypes.h"
#import "MAPIStoreGCSFolder.h"
#import "MAPIStoreGCSMessageTable.h"
#undef DEBUG
@@ -89,21 +89,31 @@
if (res->ulPropTag == PidTagChangeNumber)
{
NSString *changeNumber;
value = NSObjectFromMAPISPropValue (&res->lpProp);
changeNumber = [NSString stringWithUnsignedLongLong: [(NSNumber *)value unsignedLongLongValue]];
lastModified = [(MAPIStoreGCSFolder *)
container lastModifiedFromMessageChangeNumber: value];
container lastModifiedFromMessageChangeNumber: changeNumber];
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
//[self logWithFormat: @" c_lastmodified: %@", lastModified];
if (lastModified)
{
SEL operator;
operator = [self operatorFromRestrictionOperator: res->relop];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_lastmodified"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
operatorSelector: operator
value: lastModified];
[*qualifier autorelease];
rc = MAPIRestrictionStateNeedsEval;
}
else
rc = MAPIRestrictionStateAlwaysTrue;
{
[self logWithFormat: @"No last modified found for: 0x%.16"PRIx64". Then no restriction applied",
[value unsignedLongLongValue]];
rc = MAPIRestrictionStateAlwaysTrue;
}
}
else
{
+4 -15
View File
@@ -30,6 +30,7 @@
#import <SOGo/NSArray+Utilities.h>
#import <Mailer/SOGoMailBodyPart.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/NSDictionary+Mail.h>
#import "MAPIStoreTypes.h"
#import "MAPIStoreMailMessage.h"
@@ -108,7 +109,7 @@
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
0x68};
*data = [[NSData dataWithBytes: recordBytes length: 16]
asBinaryInMemCtx: memCtx];
@@ -117,19 +118,7 @@
- (NSString *) _fileName
{
NSString *fileName;
NSDictionary *parameters;
fileName = [[bodyInfo objectForKey: @"parameterList"]
objectForKey: @"name"];
if (!fileName)
{
parameters = [[bodyInfo objectForKey: @"disposition"]
objectForKey: @"parameterList"];
fileName = [parameters objectForKey: @"filename"];
}
return fileName;
return [bodyInfo filename];
}
- (int) getPidTagAttachLongFilename: (void **) data
@@ -178,7 +167,7 @@
- (int) getPidTagAttachContentId: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx
{
{
*data = [[bodyInfo objectForKey: @"bodyId"]
asUnicodeInMemCtx: memCtx];
+13 -11
View File
@@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
for (count = 0; count < max; count++)
{
context = talloc_zero (memCtx, struct mapistore_contexts_list);
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
// secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
currentName = [secondaryFolders objectAtIndex: count];
// To get the real name we have to revert that (applying the decode functions)
// in reverse order
// To get the real name we have to revert that (applying the decode functions
// in reverse order)
// e.g.: Problèmes de synchronisation
realName = [[currentName fromCSSIdentifier]
stringByDecodingImap4FolderName];
realName = [[[currentName substringFromIndex: 6]
fromCSSIdentifier]
stringByDecodingImap4FolderName];
// And finally to represent that as URI we have to (1) asCSSIdentifier,
// (2) Imap4Encode and (3) AddPercentEscapes
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
// (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
// In the example there are no percent escapes added because is already ok
stringData = [[[realName asCSSIdentifier]
stringByEncodingImap4FolderName]
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
stringData = [NSString stringWithFormat: @"folder%@", stringData];
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
context->name = [[realName substringFromIndex: 6] asUnicodeInMemCtx: context];
context->name = [realName asUnicodeInMemCtx: context];
context->main_folder = false;
context->role = MAPISTORE_MAIL_ROLE;
context->tag = "tag";
@@ -200,7 +202,7 @@ MakeDisplayFolderName (NSString *folderName)
andTDBIndexing: NULL];
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
folderName = [NSString stringWithFormat: @"folder%@",
[newFolderName asCSSIdentifier]];
[[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
newFolder = [SOGoMailFolder objectWithName: folderName
inContainer: accountFolder];
if ([newFolder create])
@@ -209,7 +211,7 @@ MakeDisplayFolderName (NSString *folderName)
withString: @"%40"],
[userName stringByReplacingOccurrencesOfString: @"@"
withString: @"%40"],
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
[folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
else
mapistoreURI = nil;
+2
View File
@@ -52,6 +52,8 @@
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
- (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey;
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey;
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
+198 -45
View File
@@ -68,10 +68,13 @@
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
#include <gen_ndr/exchange.h>
#undef DEBUG
#include <util/attr.h>
#include <libmapi/libmapi.h>
#include <libmapiproxy.h>
#include <limits.h>
#include <mapistore/mapistore.h>
#include <mapistore/mapistore_errors.h>
@@ -109,6 +112,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
inContainer: dbFolder]);
[versionsMessage setObjectType: MAPIInternalCacheObject];
[versionsMessage reloadIfNeeded];
}
- (BOOL) ensureFolderExists
@@ -178,7 +182,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME_UNICODE)
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszW];
else if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME)
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszA];
folderName = [NSString stringWithUTF8String: (const char *) aRow->lpProps[i].value.lpszA];
}
if (folderName)
@@ -254,6 +258,67 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
return MAPISTORE_SUCCESS;
}
- (EOQualifier *) simplifyQualifier: (EOQualifier *) qualifier
{
/* Hack: Reduce the number of MODSEQ constraints to a single one as
we assume the difference among MODSEQs will be small enough to
return a small number of UIDs.
This is the only case we do simplify:
MODSEQ >= x | MODSEQ >= y | MODSEQ >= z => MODSEQ >= min(x,y,z)
*/
if (qualifier && [qualifier isKindOfClass: [EOOrQualifier class]])
{
EOQualifier *simplifiedQualifier;
NSArray *quals;
NSNumber *minModseq;
NSUInteger i, count;
quals = [(EOOrQualifier *)qualifier qualifiers];
count = [quals count];
if (count < 2)
return qualifier;
minModseq = [NSNumber numberWithUnsignedLongLong: ULLONG_MAX];
for (i = 0; i < count; i++)
{
EOQualifier *subQualifier;
subQualifier = [quals objectAtIndex: i];
if ([subQualifier isKindOfClass: [EOAndQualifier class]]
&& [[(EOAndQualifier *)subQualifier qualifiers] count] == 1)
subQualifier = [[(EOAndQualifier *)subQualifier qualifiers] objectAtIndex: 0];
if ([subQualifier isKindOfClass: [EOKeyValueQualifier class]]
&& [[(EOKeyValueQualifier *)subQualifier key] isEqualToString: @"MODSEQ"])
{
NSNumber *value;
value = (NSNumber *)[(EOKeyValueQualifier *)subQualifier value];
if ([minModseq compare: value] == NSOrderedDescending
&& [value unsignedLongLongValue] > 0)
minModseq = (NSNumber *)[(EOKeyValueQualifier *)subQualifier value];
}
else
return qualifier;
}
if ([minModseq unsignedLongLongValue] > 0 && [minModseq unsignedLongLongValue] < ULLONG_MAX)
{
simplifiedQualifier = [[EOKeyValueQualifier alloc]
initWithKey: @"MODSEQ"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
value: minModseq];
[simplifiedQualifier autorelease];
return simplifiedQualifier;
}
}
return qualifier;
}
- (EOQualifier *) nonDeletedQualifier
{
static EOQualifier *nonDeletedQualifier = nil;
@@ -278,7 +343,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
andSortOrderings: (NSArray *) sortOrderings
{
NSArray *uidKeys;
EOQualifier *fetchQualifier;
EOQualifier *fetchQualifier, *simplifiedQualifier;
if ([self ensureFolderExists])
{
@@ -287,9 +352,10 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
if (qualifier)
{
simplifiedQualifier = [self simplifyQualifier: qualifier];
fetchQualifier
= [[EOAndQualifier alloc] initWithQualifiers:
[self nonDeletedQualifier], qualifier,
[self nonDeletedQualifier], simplifiedQualifier,
nil];
[fetchQualifier autorelease];
}
@@ -516,6 +582,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
return [modseq1 compare: modseq2];
}
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
forMessageEntry: (NSMutableDictionary *) messageEntry
{
NSData *globCnt, *oldGlobCnt;
NSMutableDictionary *changeList;
NSString *guid;
struct SizedXid *sizedXIDList;
struct XID xid;
uint32_t i, length;
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
if (!changeList)
{
changeList = [NSMutableDictionary new];
[messageEntry setObject: changeList
forKey: @"PredecessorChangeList"];
[changeList release];
}
if (sizedXIDList) {
for (i = 0; i < length; i++)
{
xid = sizedXIDList[i].XID;
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
oldGlobCnt = [changeList objectForKey: guid];
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
[changeList setObject: globCnt forKey: guid];
}
talloc_free (sizedXIDList);
}
[versionsMessage save];
}
- (void) _setChangeKey: (NSData *) changeKey
forMessageEntry: (NSMutableDictionary *) messageEntry
{
@@ -526,8 +630,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
NSMutableDictionary *changeList;
xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->GUID];
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid);
/* 1. set change key association */
@@ -553,13 +657,12 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
{
BOOL rc = YES;
uint64_t newChangeNum;
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq,
*nextModseq;
NSNumber *ti, *modseq, *lastModseq, *nextModseq;
NSString *changeNumber, *uid, *messageKey;
uint64_t lastModseqNbr;
EOQualifier *searchQualifier;
NSArray *uids, *changeNumbers;
NSUInteger count, max;
NSUInteger count, max, nFetched;
NSArray *fetchResults;
NSDictionary *result;
NSData *changeKey;
@@ -593,7 +696,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
}
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
initialLastModseq = lastModseq;
if (lastModseq)
{
lastModseqNbr = [lastModseq unsignedLongLongValue];
@@ -639,6 +741,13 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
context: NULL];
nFetched = [fetchResults count];
if (nFetched != max) {
[self errorWithFormat: @"Error fetching UIDs. Asked: %d Received: %d."
@"Check the IMAP conversation for details", max, nFetched];
return NO;
}
for (count = 0; count < max; count++)
{
result = [fetchResults objectAtIndex: count];
@@ -677,40 +786,37 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
}
/* 2. we synchronise expunged UIDs */
if (initialLastModseq)
fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
{
fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
uid = [[fetchResults objectAtIndex: count] stringValue];
if ([messages objectForKey: uid])
{
uid = [[fetchResults objectAtIndex: count] stringValue];
if ([messages objectForKey: uid])
if (!changeNumber)
{
if (!changeNumber)
{
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
else
{
[self logWithFormat:@"Message entry not found for UID %@", uid];
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
if (changeNumber)
else
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
[self logWithFormat:@"Message entry not found for UID %@", uid];
}
}
if (changeNumber)
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
}
if (foundChange)
{
@@ -924,8 +1030,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
return changeNumber;
}
- (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
{
NSMutableDictionary *messages, *messageEntry;
NSString *messageUid;
@@ -936,7 +1041,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
messageEntry = [messages objectForKey: messageUid];
if (!messageEntry)
{
[self warnWithFormat: @"attempting to synchronise to set the change key for "
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
@"this message %@", messageKey];
synced = [self synchroniseCacheForUID: messageUid];
if (synced)
@@ -947,11 +1052,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
abort ();
}
}
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
return messageEntry;
}
- (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
{
[self _setChangeKey: changeKey
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
[versionsMessage save];
}
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
{
/* Update predecessor change list property given the change key. It
returns if the change key has been added to the list or not */
BOOL added = NO;
NSData *globCnt, *oldGlobCnt;
NSDictionary *messageEntry;
NSMutableDictionary *changeList;
NSString *guid;
struct XID *xid;
xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid);
messageEntry = [self _messageEntryFromMessageKey: messageKey];
if (messageEntry)
{
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
if (changeList)
{
oldGlobCnt = [changeList objectForKey: guid];
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
{
[changeList setObject: globCnt forKey: guid];
[versionsMessage save];
added = YES;
}
}
else
[self errorWithFormat: @"Missing predecessor change list to update"];
}
return added;
}
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
{
NSDictionary *messages, *changeKeyDict;
@@ -1217,6 +1368,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx
@@ -1231,12 +1383,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
NSDictionary *result;
NSUInteger count;
NSArray *a;
NSData *changeKey;
NSData *changeList;
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
fromFolder: sourceFolder withMIDs: targetMids
andChangeKeys: targetChangeKeys
andPredecessorChangeLists: targetPredecessorChangeLists
wantCopy: wantCopy
inMemCtx: memCtx];
@@ -1325,11 +1478,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
[self synchroniseCache];
for (count = 0; count < midCount; count++)
{
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
messageKey = [NSString stringWithFormat: @"%@.eml",
[destUIDs objectAtIndex: count]];
[self setChangeKey: changeKey
forMessageWithKey: messageKey];
[self _updatePredecessorChangeListWith: changeList
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
}
}
@@ -1558,10 +1711,10 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser]
&& [roles containsObject: SOGoRole_FolderEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoRole_ObjectEditor])
rights |= RightsEditAll;
rights |= RightsEditAll | RightsEditOwn;
if ([roles containsObject: SOGoRole_ObjectViewer])
rights |= RightsReadItems;
if ([roles containsObject: SOGoRole_FolderCreator])
+95 -43
View File
@@ -40,6 +40,7 @@
#import <Mailer/NSData+Mail.h>
#import <Mailer/SOGoMailBodyPart.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/NSDictionary+Mail.h>
#import "Codepages.h"
#import "NSData+MAPIStore.h"
@@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
count1 = [keys indexOfObject: data1];
data2 = [entry2 objectForKey: @"mimeType"];
count2 = [keys indexOfObject: data2];
if (count1 == count2)
{
data1 = [entry1 objectForKey: @"key"];
@@ -339,7 +340,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
wrapperWithICalEvent: event
andUser: [context activeUser]
andSenderEmail: senderEmail
inTimeZone: [[self userContext] timeZone]
withConnectionInfo: [context connectionInfo]];
[appointmentWrapper retain];
}
@@ -521,6 +521,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSUInteger colIdx;
NSString *stringValue;
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
or less characters followed by a colon at the beginning of
the subject, we can assume that's the subject prefix */
subject = [self subject];
colIdx = [subject rangeOfString: @":"].location;
if (colIdx != NSNotFound && colIdx < 4)
@@ -529,24 +532,52 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
else
stringValue = @"";
*data = [stringValue asUnicodeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
- (int) getPidTagNormalizedSubject: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx
{
NSString *subject;
NSUInteger colIdx;
NSString *stringValue;
NSString *stringValue, *subject;
NSUInteger quoteStartIdx, quoteEndIdx, colIdx;
NSRange quoteRange;
if (!headerSetup)
[self _fetchHeaderData];
subject = [self subject];
colIdx = [subject rangeOfString: @":"].location;
if (colIdx != NSNotFound && colIdx < 4)
stringValue = [[subject substringFromIndex: colIdx + 1]
stringByTrimmingLeadSpaces];
if (mailIsMeetingRequest)
{
/* SOGo "spices up" the invitation/update mail's subject, but
the client uses it to name the attendee's event, so we keep
only what's inside the quotes */
quoteStartIdx = [subject rangeOfString: @"\""].location;
quoteEndIdx = [subject rangeOfString: @"\""
options: NSBackwardsSearch].location;
if (quoteStartIdx != NSNotFound
&& quoteEndIdx != NSNotFound
&& quoteStartIdx != quoteEndIdx)
{
quoteRange = NSMakeRange(quoteStartIdx + 1, quoteEndIdx - quoteStartIdx - 1);
stringValue = [subject substringWithRange: quoteRange];
}
else stringValue = subject;
}
else
stringValue = subject;
{
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
or less characters followed by a colon at the beginning of
the subject, we can assume that's the subject prefix */
colIdx = [subject rangeOfString: @":"].location;
if (colIdx != NSNotFound && colIdx < 4)
stringValue = [[subject substringFromIndex: colIdx + 1]
stringByTrimmingLeadSpaces];
else
stringValue = subject;
}
if (!stringValue)
stringValue = @"";
*data = [stringValue asUnicodeInMemCtx: memCtx];
@@ -624,7 +655,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSDictionary *coreInfos;
NSArray *flags;
unsigned int v = 0;
coreInfos = [sogoObject fetchCoreInfos];
flags = [coreInfos objectForKey: @"flags"];
@@ -636,7 +667,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if ([[self attachmentKeys]
count] > 0)
v |= MSGFLAG_HASATTACH;
*data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS;
@@ -656,7 +687,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
v = 2;
else
v = 0;
*data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS;
@@ -668,15 +699,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSDictionary *coreInfos;
NSArray *flags;
unsigned int v;
coreInfos = [sogoObject fetchCoreInfos];
flags = [coreInfos objectForKey: @"flags"];
if ([flags containsObject: @"flagged"])
v = 6;
else
v = 0;
*data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS;
@@ -755,7 +786,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if ([ngAddress isKindOfClass: [NGMailAddress class]])
{
cn = [ngAddress displayName];
// If we don't have a displayName, we use the email address instead. This
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
// nor in the recipient field if we reply to the email.
@@ -779,7 +810,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSDictionary *contactInfos;
NGMailAddress *ngAddress;
NSData *entryId;
struct ldb_context *samCtx;
int rc;
if (fullMail)
@@ -802,14 +832,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if (contactInfos)
{
username = [contactInfos objectForKey: @"sAMAccountName"];
samCtx = [[self context] connectionInfo]->sam_ctx;
entryId = MAPIStoreInternalEntryId (samCtx, username);
entryId = MAPIStoreInternalEntryId([[self context] connectionInfo], username);
}
else
entryId = MAPIStoreExternalEntryId (cn, email);
*data = [entryId asBinaryInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
else
@@ -966,15 +995,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
{
uint32_t v;
NSString *s;
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
v = 0x1;
if ([s hasPrefix: @"1"]) v = 0x2;
else if ([s hasPrefix: @"2"]) v = 0x2;
else if ([s hasPrefix: @"4"]) v = 0x0;
else if ([s hasPrefix: @"5"]) v = 0x0;
*data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS;
@@ -1163,14 +1192,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if (!headerSetup)
[self _fetchHeaderData];
if ([headerMimeType isEqualToString: @"text/plain"])
format = EDITOR_FORMAT_PLAINTEXT;
else if ([headerMimeType isEqualToString: @"text/html"])
format = EDITOR_FORMAT_HTML;
else
format = 0; /* EDITOR_FORMAT_DONTKNOW */
*data = MAPILongValue (memCtx, format);
return MAPISTORE_SUCCESS;
@@ -1446,11 +1475,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSData *entryId;
NSDictionary *contactInfos;
SOGoUserManager *mgr;
struct ldb_context *samCtx;
struct mapistore_message *msgData;
struct mapistore_message_recipient *recipient;
samCtx = [[self context] connectionInfo]->sam_ctx;
[super getMessageData: &msgData inMemCtx: memCtx];
if (!headerSetup)
@@ -1504,7 +1531,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (samCtx, username);
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], username);
}
else
{
@@ -1517,7 +1544,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
p = 0;
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
p++;
@@ -1525,7 +1552,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
recipient->data[p] = MAPILongValue (msgData, 0);
p++;
// PR_7BIT_DISPLAY_NAME_UNICODE
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
p++;
@@ -1533,7 +1560,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
// PR_SMTP_ADDRESS_UNICODE
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
p++;
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
p++;
@@ -1566,12 +1593,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
withPrefix: (NSString *) keyPrefix
{
NSArray *parts;
NSDictionary *parameters;
NSUInteger count, max;
parameters = [[bodyInfo objectForKey: @"disposition"]
objectForKey: @"parameterList"];
if ([[parameters objectForKey: @"filename"] length] > 0)
if ([[bodyInfo filename] length] > 0)
{
if ([keyPrefix length] == 0)
keyPrefix = @"0";
@@ -1645,15 +1669,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
return attachment;
}
- (int) setReadFlag: (uint8_t) flag
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
NSString *imapFlag = @"\\Seen";
/* TODO: notifications should probably be emitted from here */
if (flag & CLEAR_READ_FLAG)
[sogoObject removeFlags: imapFlag];
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
else
[sogoObject addFlags: imapFlag];
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
return MAPISTORE_SUCCESS;
}
@@ -1694,9 +1716,12 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
return nil;
}
- (void) save: (TALLOC_CTX *) memCtx
- (void) save: (TALLOC_CTX *) memCtx
{
BOOL modified = NO;
BOOL seen, storedSeenFlag;
NSNumber *value;
NSString *imapFlag = @"\\Seen";
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
if (value)
@@ -1706,8 +1731,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
[sogoObject addFlags: @"\\Flagged"];
else /* 0: unflagged, 1: follow up complete */
[sogoObject removeFlags: @"\\Flagged"];
modified = YES;
}
/* Manage seen flag on save */
value = [properties objectForKey: @"read_flag_set"];
if (value)
{
seen = [value boolValue];
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
/* We modify the flags anyway to generate a new change number */
if (seen)
{
if (storedSeenFlag)
[sogoObject removeFlags: imapFlag];
[sogoObject addFlags: imapFlag];
}
else
{
if (!storedSeenFlag)
[sogoObject addFlags: imapFlag];
[sogoObject removeFlags: imapFlag];
}
modified = YES;
}
if (modified)
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
if (mailIsSharingObject)
[[self _sharingObject] saveWithMessage: self
andSOGoObject: sogoObject];
+22 -7
View File
@@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
if (modseq)
modseq = [NSNumber numberWithUnsignedLongLong:
[modseq unsignedLongLongValue] + 1];
{
if (res->relop == RELOP_GT)
modseq = [NSNumber numberWithUnsignedLongLong:
[modseq unsignedLongLongValue] + 1];
}
else
modseq = [NSNumber numberWithUnsignedLongLong: 0];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
value: modseq];
[*qualifier autorelease];
rc = MAPIRestrictionStateNeedsEval;
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
{
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
value: modseq];
[*qualifier autorelease];
rc = MAPIRestrictionStateNeedsEval;
}
else
{
/* Ignore other operations as IMAP only support MODSEQ >= X */
[self warnWithFormat: @"Ignoring '%@' as only supported operators are > and >=",
NSStringFromSelector ([self operatorFromRestrictionOperator: res->relop])];
rc = MAPIRestrictionStateAlwaysTrue;
}
}
break;
+50 -27
View File
@@ -34,6 +34,7 @@
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.h>
#import <NGExtensions/NSString+Encoding.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeMultipartBody.h>
@@ -166,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
- (BOOL) hasContentId
{
return ([properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
!= nil);
NSString *contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
return contentId && [contentId length] > 0;
}
- (NGMimeBodyPart *) asMIMEBodyPart
@@ -232,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
[map addObject: contentDisposition forKey: @"content-disposition"];
contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
if (contentId)
if (contentId && [contentId length] > 0)
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
forKey: @"content-id"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
@@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
version = [properties objectForKey: @"version"];
return (version
? exchange_globcnt ([version unsignedLongLongValue])
? [version unsignedLongLongValue]
: ULLONG_MAX);
}
@@ -337,13 +338,10 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
NSData *entryId;
NSDictionary *allRecipients, *dict, *contactInfos;
SOGoUserManager *mgr;
struct ldb_context *samCtx;
struct mapistore_message *msgData;
struct mapistore_message_recipient *recipient;
enum ulRecipClass type;
samCtx = [[self context] connectionInfo]->sam_ctx;
// [super getMessageData: &msgData inMemCtx: memCtx];
msgData = talloc_zero (memCtx, struct mapistore_message);
@@ -388,7 +386,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (samCtx, username);
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], username);
}
else
{
@@ -681,6 +679,14 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
if (!fromResolved)
{
TALLOC_CTX *local_mem_ctx;
local_mem_ctx = talloc_new(NULL);
if (!local_mem_ctx)
{
NSLog (@"%s: Out of memory", __PRETTY_FUNCTION__);
return;
}
NSLog (@"Message without an orig from, try to guess it from PidTagSenderEntryId");
senderEntryId = [mailProperties objectForKey: MAPIPropertyKey (PR_SENDER_ENTRYID)];
if (senderEntryId)
@@ -694,12 +700,12 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
bin32.cb = [senderEntryId length];
bin32.lpb = (uint8_t *) [senderEntryId bytes];
addrBookEntryId = get_AddressBookEntryId (connInfo->sam_ctx, &bin32);
addrBookEntryId = get_AddressBookEntryId (local_mem_ctx, &bin32);
if (addrBookEntryId && [[NSString stringWithGUID: &addrBookEntryId->ProviderUID]
hasSuffix: @"08002b2fe182"])
{
/* TODO: better way to distinguish local and other ones */
username = MAPIStoreSamDBUserAttribute (connInfo->sam_ctx, @"legacyExchangeDN",
username = MAPIStoreSamDBUserAttribute (connInfo, @"legacyExchangeDN",
[NSString stringWithUTF8String: addrBookEntryId->X500DN], @"sAMAccountName");
if (username)
{
@@ -720,7 +726,7 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
/* Try with One-Off EntryId */
struct OneOffEntryId *oneOffEntryId;
oneOffEntryId = get_OneOffEntryId (connInfo->sam_ctx, &bin32);
oneOffEntryId = get_OneOffEntryId (local_mem_ctx, &bin32);
if (oneOffEntryId && [[NSString stringWithGUID: &oneOffEntryId->ProviderUID]
hasSuffix: @"00dd010f5402"])
{
@@ -729,9 +735,7 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->EmailAddress.lpszW]
forKey: @"email"];
}
talloc_free (oneOffEntryId);
}
talloc_free (addrBookEntryId);
if ([[fromRecipient allKeys] count] > 0)
{
@@ -741,6 +745,8 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
}
}
/* Free entryId */
talloc_free(local_mem_ctx);
}
if (!recipients)
@@ -1007,7 +1013,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
- (NGMimeMessage *) _generateMessageWithBcc: (BOOL) withBcc
{
NSString *contentType;
NGMimeMessage *message;
NGMimeMessage *message;
NGMutableHashMap *headers;
id messageBody;
@@ -1047,6 +1053,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
- (int) submitWithFlags: (enum SubmitFlags) flags
{
enum mapistore_error rc = MAPISTORE_SUCCESS;
NSDictionary *recipients;
NSData *messageData;
NSMutableArray *recipientEmails;
@@ -1093,7 +1100,10 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
withAuthenticator: authenticator
inContext: woContext];
if (error)
[self logWithFormat: @"an error occurred: '%@'", error];
{
[self errorWithFormat: @"an error occurred: '%@'", error];
rc = MAPISTORE_ERR_MSG_SEND;
}
// mapping = [self mapping];
// [mapping unregisterURLWithID: [self objectId]];
@@ -1105,12 +1115,13 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
[self logWithFormat: @"skipping submit of message with class '%@'",
msgClass];
return MAPISTORE_SUCCESS;
return rc;
}
- (void) save: (TALLOC_CTX *) memCtx
{
NSString *folderName, *flag, *newIdString, *messageKey;
BOOL updatedMetadata;
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
NSData *changeKey, *messageData;
NGImap4Connection *connection;
NGImap4Client *client;
@@ -1146,21 +1157,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
[sogoObject setNameInContainer: messageKey];
[mapping registerURL: [self url] withID: mid];
/* synchronise the cache and update the change key with the one provided
by the client. Before doing this, lets issue a unselect/select combo
because of timing issues with Dovecot in obtaining the latest modseq.
Sometimes, Dovecot doesn't return the newly appended UID if we do
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
/* synchronise the cache and update the predecessor change list
with the change key provided by the client. Before doing
this, lets issue a unselect/select combo because of timing
issues with Dovecot in obtaining the latest modseq.
Sometimes, Dovecot doesn't return the newly appended UID if
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
IMAP APPEND */
[client unselect];
[client select: folderName];
[(MAPIStoreMailFolder *) container synchroniseCache];
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
if (changeKey)
[(MAPIStoreMailFolder *) container
setChangeKey: changeKey
forMessageWithKey: messageKey];
{
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
forMessageWithKey: messageKey];
if (!updatedMetadata)
[self warnWithFormat: @"Predecessor change list not updated with client data"];
}
/* Update version property (PR_CHANGE_KEY indeed) as it is
requested once it is saved */
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
if (changeNumber)
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
forKey: @"version"];
}
}
+1 -1
View File
@@ -63,7 +63,7 @@
withAID: (uint32_t) aid;
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
andRowCount: (uint32_t *) countPtr;
- (int) setReadFlag: (uint8_t) flag;
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
- (NSArray *) activeContainerMessageTables;
+3 -2
View File
@@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
}
[self save: memCtx];
/* We make sure that any change-related properties are removes from the
/* We make sure that any change-related properties are removed from the
properties dictionary, to make sure that related methods will be
invoked the next time they are requested. */
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
if ([container isKindOfClass: MAPIStoreFolderK])
{
@@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
return [self getNo: data inMemCtx: memCtx];;
}
- (int) setReadFlag: (uint8_t) flag
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
// [self subclassResponsibility: _cmd];
+1 -1
View File
@@ -103,7 +103,7 @@
else
{
connInfo = [(MAPIStoreContext *) [container context] connectionInfo];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, userId);
entryId = MAPIStoreInternalEntryId (connInfo, userId);
}
*data = [entryId asBinaryInMemCtx: memCtx];
+2 -6
View File
@@ -25,12 +25,9 @@
#include <talloc.h>
#import <Foundation/NSTimeZone.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalRecurrenceRule.h>
@class NSTimeZone;
#import <NGCards/iCalTimeZone.h>
@class iCalEvent;
@class iCalRepeatableEntityObject;
@@ -46,7 +43,7 @@
fromRecurrencePattern: (struct RecurrencePattern *) rp
withExceptions: (struct ExceptionInfo *) exInfos
andExceptionCount: (uint16_t) exInfoCount
inTimeZone: (NSTimeZone *) tz;
inTimeZone: (iCalTimeZone *) tz;
@end
@@ -54,7 +51,6 @@
- (void) fillRecurrencePattern: (struct RecurrencePattern *) rp
withEvent: (iCalEvent *) event
inTimeZone: (NSTimeZone *) timeZone
inMemCtx: (TALLOC_CTX *) memCtx;
@end
+6 -8
View File
@@ -24,7 +24,6 @@
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
@@ -35,6 +34,7 @@
#import <NGCards/iCalRepeatableEntityObject.h>
#import <NGCards/iCalRecurrenceRule.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import "NSDate+MAPIStore.h"
#import "MAPIStoreRecurrenceUtils.h"
@@ -51,7 +51,7 @@
fromRecurrencePattern: (struct RecurrencePattern *) rp
withExceptions: (struct ExceptionInfo *) exInfos
andExceptionCount: (uint16_t) exInfoCount
inTimeZone: (NSTimeZone *) tz
inTimeZone: (iCalTimeZone *) tz
{
NSCalendarDate *startDate, *olEndDate, *untilDate, *exDate;
@@ -63,7 +63,7 @@
iCalWeekOccurrence weekOccurrence;
iCalWeekOccurrences dayMaskDays;
NSUInteger count, max;
NSInteger bySetPos;
NSInteger bySetPos, tzOffset;
unsigned char maskValue;
[entity removeAllRecurrenceRules];
@@ -242,9 +242,10 @@
{
/* The OriginalStartDate is in local time */
exDate = [NSDate dateFromMinutesSince1601: exInfos[count].OriginalStartDate];
tzOffset = -[[tz periodForDate: exDate] secondsOffsetFromGMT];
exDate = [exDate dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: - [tz secondsFromGMT]];
seconds: tzOffset];
[exceptionDates removeObject: exDate];
}
}
@@ -263,7 +264,6 @@
- (void) fillRecurrencePattern: (struct RecurrencePattern *) rp
withEvent: (iCalEvent *) event
inTimeZone: (NSTimeZone *) timeZone
inMemCtx: (TALLOC_CTX *) memCtx
{
iCalRecurrenceFrequency freq;
@@ -279,10 +279,8 @@
NSMutableArray *deletedDates, *modifiedDates;
startDate = [event firstRecurrenceStartDate];
[startDate setTimeZone: timeZone];
endDate = [event lastPossibleRecurrenceStartDate];
[endDate setTimeZone: timeZone];
rp->ReaderVersion = 0x3004;
rp->WriterVersion = 0x3004;
+46 -44
View File
@@ -64,11 +64,11 @@ static BOOL initialization_done = NO;
} \
OC_DEBUG(5, "[SOGo] --->");
#define NS_CURRENT_THREAD_TRY_UNREGISTER() \
#define NS_CURRENT_THREAD_TRY_UNREGISTER(rc) \
if (__nsrct_thread_registered) { \
GSUnregisterCurrentThread(); \
} \
OC_DEBUG(6, "[SOGo] <---");
OC_DEBUG(6, "[SOGo] <--- [%s]", mapistore_errstr (rc));
#define TRYCATCH_START @try {
#define TRYCATCH_END(pool) \
@@ -76,7 +76,7 @@ static BOOL initialization_done = NO;
enum mapistore_error ret = sogo_backend_handle_objc_exception(e, __PRETTY_FUNCTION__, __LINE__); \
mapiapp_cleanup(); \
[pool release]; \
NS_CURRENT_THREAD_TRY_UNREGISTER(); \
NS_CURRENT_THREAD_TRY_UNREGISTER(ret); \
return ret; \
} \
mapiapp_cleanup();
@@ -253,7 +253,7 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx,
rc = MAPISTORE_ERROR;
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
return rc;
}
@@ -291,7 +291,7 @@ sogo_backend_create_root_folder (const char *username,
rc = MAPISTORE_ERROR;
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
return rc;
}
@@ -322,7 +322,7 @@ sogo_backend_list_contexts(const char *username, struct indexing_context *indexi
rc = MAPISTORE_ERROR;
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
return rc;
}
@@ -363,7 +363,7 @@ sogo_context_get_path(void *backend_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -397,7 +397,7 @@ sogo_context_get_root_folder(void *backend_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -439,7 +439,7 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -480,7 +480,7 @@ sogo_folder_create_folder(void *folder_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -519,7 +519,7 @@ sogo_folder_delete(void *folder_object)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -549,7 +549,7 @@ sogo_folder_get_child_count(void *folder_object, enum mapistore_table_type table
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -588,7 +588,7 @@ sogo_folder_open_message(void *folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -627,7 +627,7 @@ sogo_folder_create_message(void *folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -657,7 +657,7 @@ sogo_folder_delete_message(void *folder_object, uint64_t mid, uint8_t flags)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
uint32_t mid_count,
uint64_t *src_mids, uint64_t *t_mids,
struct Binary_r **target_change_keys,
struct Binary_r **target_predecessor_change_lists,
uint8_t want_copy)
{
MAPIStoreFolder *sourceFolder, *targetFolder;
@@ -698,12 +699,13 @@ sogo_folder_move_copy_messages(void *folder_object,
fromFolder: sourceFolder
withMIDs: t_mids
andChangeKeys: target_change_keys
andPredecessorChangeLists: target_predecessor_change_lists
wantCopy: want_copy
inMemCtx: mem_ctx];
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -751,7 +753,7 @@ sogo_folder_move_folder(void *folder_object, void *target_folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -793,7 +795,7 @@ sogo_folder_copy_folder(void *folder_object, void *target_folder_object, TALLOC_
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -829,7 +831,7 @@ sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -867,7 +869,7 @@ sogo_folder_open_table(void *folder_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -901,7 +903,7 @@ sogo_folder_modify_permissions(void *folder_object, uint8_t flags,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -932,7 +934,7 @@ sogo_folder_preload_message_bodies(void *folder_object, enum mapistore_table_typ
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -965,8 +967,8 @@ sogo_message_get_message_data(void *message_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
rc = MAPISTORE_SUCCESS;
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -999,7 +1001,7 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1033,7 +1035,7 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1067,7 +1069,7 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1102,7 +1104,7 @@ sogo_message_modify_recipients (void *message_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
struct MAPIStoreTallocWrapper *wrapper;
NSAutoreleasePool *pool;
MAPIStoreMessage *message;
int rc;
enum mapistore_error rc;
if (message_object)
{
@@ -1132,7 +1134,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1162,7 +1164,7 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1192,7 +1194,7 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1232,7 +1234,7 @@ sogo_message_attachment_open_embedded_message (void *attachment_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1270,7 +1272,7 @@ sogo_message_attachment_create_embedded_message (void *attachment_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1300,7 +1302,7 @@ static enum mapistore_error sogo_table_get_available_properties(void *table_obje
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1331,7 +1333,7 @@ sogo_table_set_columns (void *table_object, uint16_t count, enum MAPITAGS *prope
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1364,7 +1366,7 @@ sogo_table_set_restrictions (void *table_object, struct mapi_SRestriction *restr
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1397,7 +1399,7 @@ sogo_table_set_sort_order (void *table_object, struct SSortOrderSet *sort_order,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1430,7 +1432,7 @@ sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1463,7 +1465,7 @@ sogo_table_get_row_count (void *table_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1493,8 +1495,8 @@ sogo_table_handle_destructor (void *table_object, uint32_t handle_id)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
rc = MAPISTORE_SUCCESS;
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1525,7 +1527,7 @@ static enum mapistore_error sogo_properties_get_available_properties(void *objec
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1560,7 +1562,7 @@ sogo_properties_get_properties (void *object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1590,7 +1592,7 @@ sogo_properties_set_properties (void *object, struct SRow *aRow)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1644,7 +1646,7 @@ sogo_manager_generate_uri (TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(MAPISTORE_SUCCESS);
return MAPISTORE_SUCCESS;
}
+3 -1
View File
@@ -197,6 +197,7 @@ static Class MAPIStoreFolderK;
inMemCtx: (TALLOC_CTX *) memCtx
{
int rc;
struct mapistore_connection_info *connInfo;
uint64_t obVersion;
obVersion = [self objectVersion];
@@ -204,8 +205,9 @@ static Class MAPIStoreFolderK;
rc = MAPISTORE_ERR_NOT_FOUND;
else
{
connInfo = [[self context] connectionInfo];
*data = MAPILongLongValue (memCtx, ((obVersion << 16)
| 0x0001));
| connInfo->repl_id));
rc = MAPISTORE_SUCCESS;
}
+3 -3
View File
@@ -25,13 +25,13 @@
@class NSString;
struct ldb_context;
#include <mapistore/mapistore.h>
NSString *MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
NSString *MAPIStoreSamDBUserAttribute (struct mapistore_connection_info *connInfo,
NSString *userKey,
NSString *value,
NSString *attributeName);
NSData *MAPIStoreInternalEntryId (struct ldb_context *, NSString *username);
NSData *MAPIStoreInternalEntryId (struct mapistore_connection_info *connInfo, NSString *username);
NSData *MAPIStoreExternalEntryId (NSString *cn, NSString *email);
#endif /* MAPISTORESAMDBUTILS_H */
+19 -4
View File
@@ -24,13 +24,18 @@
#import <Foundation/NSString.h>
#include <talloc.h>
#include <ldb.h>
#include <libmapiproxy.h>
#include <samba/version.h>
#import "NSData+MAPIStore.h"
#import "MAPIStoreSamDBUtils.h"
NSString *
MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
MAPIStoreSamDBUserAttribute (struct mapistore_connection_info *connInfo,
NSString *userKey,
NSString *value,
NSString *attributeName)
@@ -48,10 +53,20 @@ MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
attrs[0] = [attributeName UTF8String];
searchFormat
= [NSString stringWithFormat: @"(&(objectClass=user)(%@=%%s))", userKey];
ret = ldb_search (samCtx, memCtx, &res, ldb_get_default_basedn(samCtx),
#if SAMBA_VERSION_MAJOR <= 4 && SAMBA_VERSION_MINOR < 3
ret = ldb_search (connInfo->sam_ctx, memCtx, &res,
ldb_get_default_basedn(connInfo->sam_ctx),
LDB_SCOPE_SUBTREE, attrs,
[searchFormat UTF8String],
[value UTF8String]);
#else
ret = safe_ldb_search (&connInfo->sam_ctx, memCtx, &res,
ldb_get_default_basedn(connInfo->sam_ctx),
LDB_SCOPE_SUBTREE, attrs,
[searchFormat UTF8String],
[value UTF8String]);
#endif
if (ret == LDB_SUCCESS && res->count == 1)
{
result = ldb_msg_find_attr_as_string (res->msgs[0], attrs[0], NULL);
@@ -65,7 +80,7 @@ MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
}
NSData *
MAPIStoreInternalEntryId (struct ldb_context *samCtx, NSString *username)
MAPIStoreInternalEntryId (struct mapistore_connection_info *connInfo, NSString *username)
{
static const uint8_t const providerUid[] = { 0xdc, 0xa7, 0x40, 0xc8,
0xc0, 0x42, 0x10, 0x1a,
@@ -82,7 +97,7 @@ MAPIStoreInternalEntryId (struct ldb_context *samCtx, NSString *username)
type: 32
X500DN: variable */
legacyDN = MAPIStoreSamDBUserAttribute (samCtx, @"sAMAccountName", username,
legacyDN = MAPIStoreSamDBUserAttribute (connInfo, @"sAMAccountName", username,
@"legacyExchangeDN");
if (legacyDN)
{
+2 -2
View File
@@ -107,11 +107,11 @@
if ([roles containsObject: SOGoRole_ObjectCreator])
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoCalendarRole_PublicModifier]
&& [roles containsObject: SOGoCalendarRole_PrivateModifier]
&& [roles containsObject: SOGoCalendarRole_ConfidentialModifier])
rights |= RightsReadItems | RightsEditAll;
rights |= RightsReadItems | RightsEditAll | RightsEditOwn;
else if ([roles containsObject: SOGoCalendarRole_PublicViewer]
&& [roles containsObject: SOGoCalendarRole_PrivateViewer]
&& [roles containsObject: SOGoCalendarRole_ConfidentialViewer])
+1 -1
View File
@@ -531,7 +531,7 @@
}
[vToDo setTimeStampAsDate: now];
[sogoObject saveComponent: vCalendar];
[sogoObject saveCalendar: vCalendar];
[self updateVersions];
}
+1 -1
View File
@@ -198,7 +198,7 @@ NSObjectFromSPropValue (const struct SPropValue *value)
break;
case PT_STRING8:
result = (value->value.lpszA
? [NSString stringWithUTF8String: value->value.lpszA]
? [NSString stringWithUTF8String: (const char *) value->value.lpszA]
: (id) @"");
break;
case PT_SYSTIME:
+2
View File
@@ -73,6 +73,8 @@ static NSMapTable *contextsTable = nil;
[userContext autorelease];
[contextsTable setObject: userContext forKey: username];
}
else
[userContext activate];
return userContext;
}
+1 -1
View File
@@ -228,7 +228,7 @@
mvResult = [NSMutableArray arrayWithCapacity: mvString->cValues];
for (count = 0; count < mvString->cValues; count++)
{
subObject = [NSString stringWithUTF8String: mvString->lppszA[count]];
subObject = [NSString stringWithUTF8String: (const char *) mvString->lppszA[count]];
[mvResult addObject: subObject];
}
+2
View File
@@ -41,6 +41,8 @@
+ (id) dataWithXID: (const struct XID *) xid;
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
with: (uint32_t *) length;
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
andCnt: (NSData *) globCnt;
+40 -6
View File
@@ -22,6 +22,7 @@
#import <NGExtensions/NSObject+Logs.h>
#import "MAPIStoreTypes.h"
#import "NSObject+MAPIStore.h"
#import "NSString+MAPIStore.h"
@@ -29,6 +30,7 @@
#undef DEBUG
#include <stdbool.h>
#include <libmapi/libmapi.h>
#include <talloc.h>
#include <util/time.h>
#include <gen_ndr/exchange.h>
@@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
NSMutableData *xidData;
struct FlatUID_r flatUID;
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
[xidData appendBytes: flatUID.ab length: 16];
[xidData appendBytes: xid->Data length: xid->Size];
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
return xidData;
}
@@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
{
xid = talloc_zero (memCtx, struct XID);
[self _extractGUID: &xid->GUID];
[self _extractGUID: &xid->NameSpaceGuid];
xid->Size = max - 16;
xid->LocalId.length = max - 16;
bytes = (uint8_t *) [self bytes];
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
}
else
{
@@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
return xid;
}
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
with: (uint32_t *) length
{
struct Binary_r bin;
struct SizedXid *sizedXIDArray;
bin.cb = [self length];
bin.lpb = (uint8_t *)[self bytes];
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
if (!sizedXIDArray)
{
NSLog (@"Impossible to parse SizedXID array");
return NULL;
}
return sizedXIDArray;
}
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
{
uint64_t globCnt = 0, oGlobCnt = 0;
if ([self length] > 0)
globCnt = *(uint64_t *) [self bytes];
if ([otherGlobCnt length] > 0)
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
return MAPICNCompare (globCnt, oGlobCnt, NULL);
}
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
andCnt: (NSData *) globCnt;
{
+2
View File
@@ -37,6 +37,8 @@
- (BOOL) isNever; /* occurs on 4500-12-31 */
+ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date
andRuleYear: (uint16_t) rYear;
@end
NSComparisonResult NSDateCompare (id date1, id date2, void *);
+60 -1
View File
@@ -24,6 +24,7 @@
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import "MAPIStoreTypes.h"
#import "NSDate+MAPIStore.h"
#undef DEBUG
@@ -48,7 +49,7 @@ _setupRefDate ()
refDate = [[NSCalendarDate alloc]
initWithYear: 1601 month: 1 day: 1
hour: 0 minute: 0 second: 0
timeZone: [NSTimeZone timeZoneWithName: @"UTC"]];
timeZone: utcTZ];
}
+ (NSCalendarDate *) dateFromMinutesSince1601: (uint32_t) minutes
@@ -128,6 +129,64 @@ _setupRefDate ()
return [calDate yearOfCommonEra] == 4500;
}
+ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date
andRuleYear: (uint16_t) rYear
{
NSCalendarDate *result;
NSInteger daysToDate;
NSUInteger firstDayOfWeek, year;
/* ([MS-OXOCAL] 2.2.1.41.1) When we're provided an absolute date (i.e., it
happens once), the SYSTEMTIME structure is enough to fill the date.
When we're parsing a SYSTEMTIME field from a time zone rule, however, a
relative date can be provided for the peroidicity of its periods. In this
scenario, the wYear field is empty and we have to use the wYear field in
the parent rule */
if (date.wYear != 0)
year = date.wYear;
else
year = rYear;
/* The wDay field indicates the occurrence of the wDayOfWeek within the month.
The 5th occurrence means the last one, even if it is the 4th. */
if (date.wDay < 5)
{
result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth day: 1
hour: date.wHour minute: date.wMinute second: date.wSecond
timeZone: utcTZ];
[result autorelease];
firstDayOfWeek = [result dayOfWeek];
daysToDate = 7 * (date.wDay - 1) + date.wDayOfWeek - firstDayOfWeek;
if (date.wDayOfWeek < firstDayOfWeek)
daysToDate += 7;
result = [result dateByAddingYears: 0 months: 0 days: daysToDate
hours: 0 minutes: 0
seconds: 0];
}
else
{
result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth + 1 day: 1
hour: date.wHour minute: date.wMinute second: date.wSecond
timeZone: utcTZ];
[result autorelease];
firstDayOfWeek = [result dayOfWeek];
daysToDate = date.wDayOfWeek - firstDayOfWeek;
if (date.wDayOfWeek >= firstDayOfWeek)
daysToDate -= 7;
result = [result dateByAddingYears: 0 months: 0 days: daysToDate
hours: 0 minutes: 0
seconds: 0];
}
return result;
}
@end
NSComparisonResult
+11 -2
View File
@@ -28,6 +28,8 @@
//
//
//
@class RTFFontTable;
@interface RTFHandler : NSObject
{
NSMapTable *_charsets;
@@ -41,6 +43,11 @@
- (id) initWithData: (NSData *) theData;
- (NSMutableData *) parse;
- (RTFFontTable *) parseFontTable;
- (void) mangleInternalStateWithBytesPtr: (const char*) newBytes
andCurrentPos: (int) newCurrentPos;
@end
//
@@ -67,6 +74,7 @@
int font_index;
int color_index;
int start_pos;
const unsigned short *charset;
}
@end
@@ -77,12 +85,13 @@
{
@public
NSString *family;
NSString *charset;
unsigned char charset;
NSString *name;
unsigned int pitch;
unsigned int index;
}
- (NSString *) description;
@end
//
@@ -96,8 +105,8 @@
- (void) addFontInfo: (RTFFontInfo *) theFontInfo
atIndex: (unsigned int ) theIndex;
- (RTFFontInfo *) fontInfoAtIndex: (unsigned int ) theIndex;
- (NSString *) description;
@end
+728 -295
View File
File diff suppressed because it is too large. Load diff
+42 -5
View File
@@ -1,8 +1,8 @@
/* dbmsgreader.m - this file is part of SOGo
*
* Copyright (C) 2011-2012 Inverse inc
* 2015 Enrique J. Hernandez
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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
@@ -33,21 +33,58 @@
#import <SOGo/SOGoProductLoader.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <libmapi/libmapi.h>
#import "MAPIStoreUserContext.h"
#import <SOGo/SOGoCacheGCSObject.h>
#import <SOGo/BSONCodec.h>
#import "NSObject+PropertyList.h"
Class MAPIStoreUserContextK, SOGoCacheGCSObjectK;
Class MAPIStoreUserContextK, SOGoCacheGCSObjectK, NSStringK;
static void
DumpBSONData(NSData *data)
{
id key, value;
NSEnumerator *dictEnum;
NSDictionary *dvalue;
NSMutableString *outStr;
NSUInteger max;
dvalue = [data BSONValue];
[dvalue displayWithIndentation:0];
printf("\n");
max = [dvalue count];
dictEnum = [dvalue keyEnumerator];
NSStringK = [NSString class];
outStr = [NSMutableString stringWithFormat: @"{ %d items\n", max];
while ((key = [dictEnum nextObject]))
{
uint32_t proptag = 0;
if ([key isKindOfClass: NSStringK] && [(NSString *)key intValue] > 0)
proptag = [(NSString *)key intValue];
if (proptag > 0)
{
const char *propTagName = get_proptag_name (proptag);
NSString *propName;
if (propTagName)
propName = [NSString stringWithCString: propTagName
encoding: NSUTF8StringEncoding];
else
propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]];
[outStr appendFormat: @" %@ = ", propName];
}
else
[outStr appendFormat: @" %@ = ", key];
value = [dvalue objectForKey: key];
[outStr appendFormat: @"(%@) %@,\n", NSStringFromClass ([value class]), value];
}
[outStr appendFormat: @"}\n"];
printf ("%s\n", [outStr UTF8String]);
}
static void
@@ -67,7 +104,7 @@ DbDumpObject (NSString *username, NSString *path)
{
printf("record found: %p\n", record);
content = [[record objectForKey: @"c_content"] dataByDecodingBase64];
DumpBSONData(content);
DumpBSONData (content);
}
else
NSLog (@"record not found");
+33 -32
View File
@@ -36,6 +36,7 @@
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalTrigger.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoUser.h>
@@ -70,11 +71,12 @@
#include <mapistore/mapistore_nameid.h>
#import "iCalEvent+MAPIStore.h"
#import "iCalTimeZone+MAPIStore.h"
@implementation iCalEvent (MAPIStoreProperties)
- (void) _setupEventRecurrence: (NSData *) mapiRecurrenceData
inTimeZone: (NSTimeZone *) tz
inTimeZone: (iCalTimeZone *) tz
inMemCtx: (TALLOC_CTX *) memCtx
{
struct Binary_r *blob;
@@ -250,10 +252,8 @@
BOOL isAllDay;
iCalDateTime *start, *end;
iCalTimeZone *tz;
NSTimeZone *userTimeZone;
NSString *priority, *class = nil;
NSString *priority, *class = nil, *tzDescription = nil;
NSUInteger responseStatus = 0;
NSInteger tzOffset;
SOGoUser *ownerUser;
id value;
@@ -274,7 +274,31 @@
[self setAccessClass: @"PUBLIC"];
}
userTimeZone = [userContext timeZone];
/* Time zone = PidLidAppointmentTimeZoneDefinitionRecur
or PidLidAppointmentTimeZoneDefinition[Start|End]Display */
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionStartDisplay)];
if (!value)
{
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionEndDisplay)];
if (!value)
{
/* If PidLidtimeZoneStruct, TZID SHOULD come from PidLidTimeZoneDescription,
if PidLidAppointmentTimeZoneDefinition[Start|End]Display it MUST be derived from KeyName
(MS-OXCICAL] 2.1.3.1.1.19.1) */
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionRecur)];
tzDescription = [properties objectForKey: MAPIPropertyKey (PidLidTimeZoneDescription)];
}
}
if (value)
{
tz = [[iCalTimeZone alloc] iCalTimeZoneFromDefinition: value
withDescription: tzDescription
inMemCtx: memCtx];
}
else
/* The client is more likely to have the webmail's time zone than any other */
tz = [iCalTimeZone timeZoneForName: [[userContext timeZone] name]];
[(iCalCalendar *) parent addTimeZone: tz];
/* CREATED */
value = [properties objectForKey: MAPIPropertyKey (PidTagCreationTime)];
@@ -306,20 +330,13 @@
objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)];
if (value)
isAllDay = [value boolValue];
if (!isAllDay)
{
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[(iCalCalendar *) parent addTimeZone: tz];
}
else
tz = nil;
// recurrence-id
value
= [properties objectForKey: MAPIPropertyKey (PidLidExceptionReplaceTime)];
if (value)
[self setRecurrenceId: value];
// start
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentStartWhole)];
if (!value)
@@ -330,15 +347,7 @@
[start setTimeZone: tz];
if (isAllDay)
{
/* when user TZ is positive (East) all-day events were not
shown properly in SOGo UI. This day delay fixes it */
tzOffset = [userTimeZone secondsFromGMTForDate: value];
if (tzOffset > 0)
{
value = [value dateByAddingYears: 0 months: 0 days: 1
hours: 0 minutes: 0
seconds: 0];
}
/* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */
[start setDate: value];
[start setTimeZone: nil];
}
@@ -356,15 +365,7 @@
[end setTimeZone: tz];
if (isAllDay)
{
/* when user TZ is positive (East) all-day events were not
shown properly in SOGo UI. This day delay fixes it */
tzOffset = [userTimeZone secondsFromGMTForDate: value];
if (tzOffset > 0)
{
value = [value dateByAddingYears: 0 months: 0 days: 1
hours: 0 minutes: 0
seconds: 0];
}
/* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */
[end setDate: value];
[end setTimeZone: nil];
}
@@ -467,7 +468,7 @@
value = [properties
objectForKey: MAPIPropertyKey (PidLidAppointmentRecur)];
if (value)
[self _setupEventRecurrence: value inTimeZone: userTimeZone inMemCtx: memCtx];
[self _setupEventRecurrence: value inTimeZone: tz inMemCtx: memCtx];
/* alarm */
[self _setupEventAlarmFromProperties: properties];
+5
View File
@@ -30,6 +30,11 @@
- (struct Binary_r *) asTimeZoneStructInMemCtx: (TALLOC_CTX *) memCtx;
- (struct Binary_r *) asZoneTimeDefinitionWithFlags: (enum TZRuleFlag) flags
inMemCtx: (TALLOC_CTX *) memCtx;
- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value
withDescription: (NSString *) description
inMemCtx: (TALLOC_CTX *) memCtx;
- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date;
@end
+212 -5
View File
@@ -23,11 +23,15 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import <NGCards/iCalByDayMask.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalRecurrenceRule.h>
#import "NSString+MAPIStore.h"
#import "NSData+MAPIStore.h"
#import "NSDate+MAPIStore.h"
#include <stdbool.h>
#include <stdint.h>
@@ -36,6 +40,7 @@
#include <libmapi/libmapi.h>
#import "iCalTimeZone+MAPIStore.h"
#import "MAPIStoreTypes.h"
@interface iCalTimeZonePeriod (MAPIStorePropertiesPrivate)
@@ -103,18 +108,18 @@
{
iCalTimeZonePeriod *period;
struct TimeZoneStruct tz;
int lBias, dlBias;
int32_t lBias, dlBias;
memset (&tz, 0, sizeof (struct TimeZoneStruct));
period = [self _mostRecentPeriodWithName: @"STANDARD"];
lBias = -[period secondsOffsetFromGMT] / 60;
tz.lBias = (uint32_t) lBias;
tz.lBias = lBias;
[period _fillTZDate: &tz.stStandardDate];
period = [self _mostRecentPeriodWithName: @"DAYLIGHT"];
if (!period)
tz.stStandardDate.wMonth = 0;
dlBias = -([period secondsOffsetFromGMT] / 60) - lBias;
tz.lDaylightBias = (uint32_t) (dlBias);
tz.lDaylightBias = dlBias;
[period _fillTZDate: &tz.stDaylightDate];
tz.wStandardYear = tz.stStandardDate.wYear;
tz.wDaylightYear = tz.stDaylightDate.wYear;
@@ -153,18 +158,220 @@
period = [self _mostRecentPeriodWithName: @"STANDARD"];
rule.wYear = [[period startDate] yearOfCommonEra];
lBias = -[period secondsOffsetFromGMT] / 60;
rule.lBias = (uint32_t) lBias;
rule.lBias = lBias;
[period _fillTZDate: &rule.stStandardDate];
period = [self _mostRecentPeriodWithName: @"DAYLIGHT"];
if (!period)
rule.stStandardDate.wMonth = 0;
dlBias = -([period secondsOffsetFromGMT] / 60) - lBias;
rule.lDaylightBias = (uint32_t) (dlBias);
rule.lDaylightBias = dlBias;
[period _fillTZDate: &rule.stDaylightDate];
return set_TimeZoneDefinition (memCtx, &definition);
}
- (NSString *) _offsetStringFromOffset: (NSInteger) offset
{
NSInteger offsetHours, offsetMins;
NSString *offsetSign;
/* The offset format is, eg, "+0200" for 2 hours 0 minutes ahead */
if (offset < 0)
offsetSign = @"-";
else
offsetSign = @"+";
offsetHours = abs (offset) / 60;
offsetMins = abs (offset) % 60;
return [NSString stringWithFormat: @"%@%d%d%d%d",
offsetSign, offsetHours / 10, offsetHours % 10,
offsetMins / 10, offsetMins % 10];
}
- (NSString *) _rRuleStringFromSystemTime: (struct SYSTEMTIME) date
{
NSString *result, *byDay;
/* The conversion tables between the SYSTEMTIME fields and the RRULE ones
can be found at [MS-OXCICAL] 2.1.3.2.1 */
if (date.wDay == 5)
byDay = @"-1";
else
byDay = [NSString stringWithFormat: @"%d", date.wDay];
switch (date.wDayOfWeek)
{
case iCalWeekDaySunday:
byDay = [byDay stringByAppendingString: @"SU"];
break;
case iCalWeekDayMonday:
byDay = [byDay stringByAppendingString: @"MO"];
break;
case iCalWeekDayTuesday:
byDay = [byDay stringByAppendingString: @"TU"];
break;
case iCalWeekDayWednesday:
byDay = [byDay stringByAppendingString: @"WE"];
break;
case iCalWeekDayThursday:
byDay = [byDay stringByAppendingString: @"TH"];
break;
case iCalWeekDayFriday:
byDay = [byDay stringByAppendingString: @"FR"];
break;
case iCalWeekDaySaturday:
byDay = [byDay stringByAppendingString: @"SA"];
break;
}
result = [NSString stringWithFormat: @"FREQ=YEARLY;BYDAY=%@;BYMONTH=%d", byDay, date.wMonth];
return result;
}
- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value
withDescription: (NSString *) description
inMemCtx: (TALLOC_CTX *) memCtx
{
BOOL daylightDefined = NO, ruleFound = NO;
iCalDateTime *daylightStart, *standardStart;
iCalRecurrenceRule *daylightRRule, *standardRRule;
iCalTimeZone *tz = nil;
iCalTimeZonePeriod *daylight, *standard;
NSCalendarDate *dlStartValue, *stStartValue;
NSString *strOffsetFrom, *strOffsetTo, *tzID;
char *keyName;
struct Binary_r *binValue;
struct SYSTEMTIME initDate;
struct TimeZoneDefinition *definition;
struct TZRule rule;
uint16_t count;
binValue = [value asBinaryInMemCtx: memCtx];
definition = get_TimeZoneDefinition (memCtx, binValue);
if (!definition)
return nil;
if (!definition->cRules)
goto end;
for (count = 0; count < definition->cRules; count++)
{
/* ([MS-OXCICAL] 2.1.3.1.1.19) The TZRule with the
TZRULE_FLAG_EFFECTIVE_TZREG bit set in the TZRule flags field
is the one that MUST be exported */
if (definition->TZRules[count].flags & TZRULE_FLAG_EFFECTIVE_TZREG)
{
rule = definition->TZRules[count];
ruleFound = YES;
break;
}
}
if (!ruleFound)
goto end;
if (!description)
{
/* The cbHeader field contains the size, in bytes of the Reserved (2b),
cchKeyName (2b) keyName (variable Unicode string) and cRules (2b)
([MS-OXOCAL] 2.2.1.41). The keyName field is a non-NULL-terminated
char array. */
keyName = talloc_strndup (memCtx, definition->keyName, (definition->cbHeader - 6) / 2);
tzID = [NSString stringWithCString: keyName
encoding: [NSString defaultCStringEncoding]];
talloc_free (keyName);
}
else
tzID = [NSString stringWithString: description];
tz = [iCalTimeZone groupWithTag: @"vtimezone"];
[tz addChild: [CardElement simpleElementWithTag: @"tzid"
value: tzID]];
if (rule.stStandardDate.wMonth != 0)
daylightDefined = YES;
/* STANDARD TIME ([MS-OXCICAL] 2.1.3.1.1.19.2) */
standard = [iCalTimeZonePeriod groupWithTag: @"standard"];
/* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */
strOffsetFrom = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lDaylightBias)];
[standard addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom"
value: strOffsetFrom]];
/* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */
strOffsetTo = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lStandardBias)];
[standard addChild: [CardElement simpleElementWithTag: @"tzoffsetto"
value: strOffsetTo]];
/* DTSTART & RRULE are derived from the stStandardDate and wYear properties */
standardStart = [iCalDateTime elementWithTag: @"dtstart"];
initDate = rule.stStandardDate;
stStartValue = [NSCalendarDate dateFromSystemTime: initDate
andRuleYear: rule.wYear];
[standardStart setDateTime: stStartValue];
[standard addChild: standardStart];
if (daylightDefined)
{
standardRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]];
[standard addChild: standardRRule];
/* DAYLIGHT SAVING TIME ([MS-OXCICAL] 2.1.3.1.1.19.3) */
daylight = [iCalTimeZonePeriod groupWithTag: @"daylight"];
/* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */
[daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom"
value: strOffsetTo]];
/* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */
[daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetto"
value: strOffsetFrom]];
/* DTSTART & RRULE are derived from the stDaylightDate and wYear properties */
daylightStart = [iCalDateTime elementWithTag: @"dtstart"];
initDate = rule.stDaylightDate;
dlStartValue = [NSCalendarDate dateFromSystemTime: initDate
andRuleYear: rule.wYear];
[daylightStart setDateTime: dlStartValue];
[daylight addChild: daylightStart];
daylightRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]];
[daylight addChild: daylightRRule];
[tz addChild: daylight];
}
[tz addChild: standard];
end:
talloc_free (definition);
return tz;
}
/**
* Adjust a date in this vTimeZone to its representation in UTC
* Example: Timezone is +0001, the date is 2015-12-15 00:00:00 +0000
* it returns 2015-12-14 23:00:00 +0000
* @param date the date to adjust to the timezone.
* @return a new GMT date adjusted with the offset of the timezone.
*/
- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date
{
NSCalendarDate *tmpDate;
tmpDate = [date copy];
[tmpDate autorelease];
[tmpDate setTimeZone: utcTZ];
return [tmpDate addYear: 0 month: 0 day: 0
hour: 0 minute: 0
second: -[[self periodForDate: tmpDate] secondsOffsetFromGMT]];
}
@end
+51 -14
View File
@@ -1,27 +1,64 @@
[![SOGo Logo](http://www.sogo.nu/fileadmin/sogo/logos/sogo.logo.png)](http://www.sogo.nu/)
## Contribute
Share your calendars, address books and mails in your community with a completely free and open source solution.
SOGo is a collaborative effort in order to create the best Free and Open Source groupware solution.
Let your Mozilla Thunderbird/Lightning, Microsoft Outlook, Apple iCal/iPhone, Android and BlackBerry users collaborate using a modern platform.
There are multiple ways you can contribute to the project
## What is SOGo?
* Documentation reviews, enhancements and translations
* Feature requests or by sharing your ideas (see the roadmap)
* Participate in the discussion on mailing lists
* Patches for bugs or enhancements
* Provide new translations
SOGo is [fully supported](http://www.sogo.nu/support/commercial_support.html) and [trusted](http://www.sogo.nu/tour/testimonials.html) groupware server with a focus on scalability and open standards. SOGo is released under the GNU GPL/LGPL v2 and above.
## Source Code
SOGo provides a rich AJAX-based Web interface and supports multiple native clients through the use of standard protocols such as CalDAV, CardDAV and GroupDAV.
You can browse the lastest version of the source code online from Inverse's github repository:
SOGo is the missing component of your infrastructure; it sits in the middle of your servers to offer your users an uniform and complete interface to access their information. It has been deployed in production environments where thousands of users are involved.
[https://github.com/inverse-inc](https://github.com/inverse-inc)
[![Sogo diagram](http://www.sogo.nu/uploads/pics/sogo.png)](http://www.sogo.nu/)
To compile SOGo, you first need to obtain the source code of both SOGo and SOPE. The source code of SOPE and SOGo can be obtained from Inverse's github repository:
[Learn more...](http://www.sogo.nu/about/overview.html)
[https://github.com/inverse-inc/sope](https://github.com/inverse-inc/sope)
## Microsoft Outlook Compatible
[https://github.com/inverse-inc/sogo](https://github.com/inverse-inc/sogo)
SOGo offers native Microsoft Outlook compatibility using the [OpenChange](http://www.openchange.org/) backend developed by [Inverse](http://www.inverse.ca/). This means Microsoft Outlook 2003, 2007, 2010 and 2013 can talk directly to SOGo &ndash; just like if it was a Microsoft Exchange server. **No plugins are required in Outlook to make this work**.
The source code of the SOGo Connector and SOGo Integrator extensions for Thunderbird 31+ can be obtained from Inverse's github repository:
The following screencast shows Outlook 2010 connected to SOGo as if it was an Exchange server, along the Web interface of SOGo.
[https://github.com/inverse-inc/sogo-connector.tb31](https://github.com/inverse-inc/sogo-connector.tb31)
[![SOGo Screencast](http://img.youtube.com/vi/1hcBSB4Kxww/0.jpg)](http://www.youtube.com/watch?v=1hcBSB4Kxww)
[https://github.com/inverse-inc/sogo-integrator.tb31](https://github.com/inverse-inc/sogo-integrator.tb31)
[![SOGo on GitHub](http://www.sogo.nu/fileadmin/images/icon.github.png)](https://github.com/inverse-inc/sogo/) [![SOGo Ohloh](http://www.sogo.nu/fileadmin/images/icon.ohloh.png)](https://www.ohloh.net/p/sogo) [![SOGo on Twitter](http://www.sogo.nu/fileadmin/images/icon.twitter.png)](http://twitter.com/scalableogo) [![SOGo on SourceForge](http://www.sogo.nu/fileadmin/images/icon.sourceforge.png)](http://sourceforge.net/projects/sogo-zeg) [![SOGo on free code](http://www.sogo.nu/fileadmin/images/icon.freecode.png)](https://freecode.com/projects/sogo-inverse) [![SOGo on LinkedIn](http://www.sogo.nu/fileadmin/images/icon.linkedin.png)](http://www.linkedin.com/groups/SOGo-Groupware-4164805)
Please refer to the FAQ section of this Web site for compilation instructions.
## Translations
SOGo and its associated components are available in various languages. The following list describes the official translations alongside their maintainers :
* English - [Inverse inc.](http://www.inverse.ca/)
* Arabic - Anass Ahmed
* Brazilian Portuguese - Alexandre Marcilio
* Catalan - Hector M. Rulot Segovia
* Croatian - Jens Riecken
* Czech -
* Danish (Denmark) -
* Dutch - Roel van Os
* French - [Inverse](http://www.inverse.ca/)
* German - Alexander Greiner-Baer
* Hungarian -
* Icelandic -
* Italian - Alessio Fattorini
* Jan Ivar Karlsen
* Polish - Paweł Bogusławski
* Russian - Alex Kabakaev
* Slovak - Martin Pastor
* Spanish (Spain) - Dominique Couot,
* Spanish (Argentina) - Federico Alberto Sayd
* Swedish - Peter Johansson
* Ukrainian - Oleksa Stasevych
* Welsh - Iona Bailey
If you would like to translate the software in an other language, please consult the FAQ. You can also subscribe to the SOGo i18n mailing list to follow string additions or changes:
To subscribe to i18n@sogo.nu, send an email to i18n-request@sogo.nu with SUBSCRIBE i18n as the subject.
Online archives of the mailing list are available from :
[https://inverse.ca/sogo/lists/arc/i18n](https://inverse.ca/sogo/lists/arc/i18n)
+1 -1
View File
@@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
ms = [NSMutableString stringWithCapacity: 256];
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]];
[ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
[ms appendString: @">"];
return ms;
+1 -1
View File
@@ -414,7 +414,7 @@
if (group)
[str appendFormat: @"%@ (group: %@)\n", tag, group];
else
[str appendFormat: @"%@\n", tag, group];
[str appendFormat: @"%@\n", tag];
[str appendString: [self versitString]];
+1 -1
View File
@@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
max = [children count];
if (max > 0)
{
[str appendFormat: @"\n %d children: {\n", [children count]];
[str appendFormat: @"\n %d children: {\n", (int)[children count]];
for (count = 0; count < max; count++)
[str appendFormat: @" %@\n",
[[children objectAtIndex: count] description]];
+5 -5
View File
@@ -32,16 +32,16 @@
- (NSString *) iCalFormattedDateTimeString
{
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear],
[self dayOfMonth], [self hourOfDay],
[self minuteOfHour], [self secondOfMinute]];
(int)[self yearOfCommonEra], (int)[self monthOfYear],
(int)[self dayOfMonth], (int)[self hourOfDay],
(int)[self minuteOfHour], (int)[self secondOfMinute]];
}
- (NSString *) iCalFormattedDateString
{
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear],
[self dayOfMonth]];
(int)[self yearOfCommonEra], (int)[self monthOfYear],
(int)[self dayOfMonth]];
}
@end
+15
View File
@@ -158,16 +158,31 @@
NSMutableString *string;
unsigned int len, i;
unichar c;
BOOL isQuoted;
len = [self length];
string = [NSMutableString stringWithCapacity: len * 1.5];
isQuoted = NO;
for (i = 0; i < len; i++)
{
c = [self characterAtIndex: i];
if (isQuoted)
{
if (c == '"')
isQuoted = NO;
[string appendFormat: @"%C", c];
continue;
}
switch (c)
{
case '"':
isQuoted = YES;
[string appendFormat: @"%C", c];
break;
case '\\':
[string appendString: @"\\\\"];
break;
+6 -2
View File
@@ -27,6 +27,7 @@
#import "NSCalendarDate+NGCards.h"
#import "iCalAlarm.h"
#import "iCalCalendar.h"
#import "iCalDateTime.h"
#import "iCalEntityObject.h"
#import "iCalEvent.h"
@@ -268,9 +269,12 @@
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
{
iCalDateTime* recurrenceId;
BOOL isMasterAllDay;
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
[recurrenceId setDate: newRecId];
else
[recurrenceId setDateTime: newRecId];
@@ -436,7 +440,7 @@
stringAttach = [[elements objectAtIndex: count] flattenedValuesForKey: @""];
url = [NSURL URLWithString: stringAttach];
if (!url && [stringAttach length] > 0)
if (![url scheme] && [stringAttach length] > 0)
url = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", stringAttach]];
[attachUrls addObject: [url absoluteString]];
@@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]])
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay+1] = YES;
currentPos++;
}
@@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]])
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay] = YES;
currentPos--;
}
+1 -1
View File
@@ -267,7 +267,7 @@
- (BOOL)isEqual:(id)_other {
if(_other == nil)
return NO;
if([_other class] != self->isa)
if([_other class] != object_getClass(self))
return NO;
if([_other hash] != [self hash])
return NO;
+3 -5
View File
@@ -1,6 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
Copyright (C) 2006-2016 Inverse inc.
This file is part of SOPE.
@@ -291,6 +291,8 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
NSString *frequency;
iCalRecurrenceFrequency freq;
freq = 0;
if ([value length] > 0)
{
frequency = [value uppercaseString];
@@ -308,11 +310,7 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
freq = iCalRecurrenceFrequenceMinutely;
else if ([frequency isEqualToString:@"SECONDLY"])
freq = iCalRecurrenceFrequenceSecondly;
else
freq = NSNotFound;
}
else
freq = NSNotFound;
return freq;
}
+36 -4
View File
@@ -161,8 +161,11 @@
minute: [tzStart minuteOfHour] second: 0
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
day: 0 hour: 0 minute: 0
tmpDate = [tmpDate addYear: 0
month: ((pos > 0) ? 0 : 1)
day: 0
hour: 0
minute: 0
second: 0];
/* If the day of the time change is "-XSU", we need to determine whether the
@@ -197,12 +200,41 @@
END:STANDARD
END:VTIMEZONE
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
effectively shift the time change by a whole week.
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
The time change in November must occur on October 25th but since tmpDate will be November 1st,
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
which is incorrect because it would shift the timezone change one week earlier. We take care about this
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
BEGIN:VCALENDAR
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
*/
if (dayOfWeek == dateDayOfWeek)
if (dayOfWeek == dateDayOfWeek && pos >= 0)
pos--;
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
+1 -1
View File
@@ -57,7 +57,7 @@
- (NSDictionary *) asDictionary
{
NSDictionary *data;
NSString *duration, *relation, *reference, *quantity, *unit;
NSString *duration, *relation, *reference, *quantity=@"", *unit=@"";
NSUInteger i;
unichar c;
@@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn)
{
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _data, [_data length]);
__PRETTY_FUNCTION__, _data, (int)[_data length]);
}
if ((len = [_data length]) == 0)
@@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn)
{
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _source, [_source length]);
__PRETTY_FUNCTION__, _source, (int)[_source length]);
}
if (!_sysId) _sysId = @"<string>";
[self _parseString: _source];
-309
View File
@@ -1,309 +0,0 @@
#!/usr/bin/env python
import getopt
import imaplib
import ldb
import plistlib
import os
import re
import shutil
import subprocess
import sys
from samba.param import LoadParm
imaphost = '127.0.0.1'
imapport = 143
samba_lp = LoadParm()
sambaprivate = samba_lp.get("private dir")
mapistorefolder = samba_lp.private_path("mapistore")
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")
# - takes a username and optionally its password
# - removes the entry in samba's ldap tree via ldbedit (NOTYET)
# - remove the user's directory under mapistore/ and mapistore/SOGo
# - cleanup Junk Folders and Sync Issues imap folders
# - Delete the sogo_cache_folder_ table for the username.
def usage():
print """
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
-i imaphost IMAP host to connect to [%s]
-p imappost IMAP port to use [%d]
-s sambaprivate samba private directory [%s]
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)
def main():
global sambaprivate
global mapistorefolder
global imaphost
global imapport
try:
opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
for o, a in opts:
if o == "-i":
imaphost = a
elif o == "-p":
imapport = a
elif o == "-s":
sambaprivate = a
mapistorefolder = "%s/mapistore" % (sambaprivate)
else:
assert False, "unhandled option"
argslen = len(args)
if (argslen == 2):
username = args[0]
userpass = args[1]
elif (argslen == 1):
username = args[0]
userpass = username
print "Using username as password"
else:
usage()
print "Specify a user (and optionally the password)"
sys.exit(2)
# cleanup starts here
try:
imapCleanup(imaphost, imapport, username, userpass)
except Exception as e:
print "Error during imapCleanup, continuing: %s" % str(e)
try:
mapistoreCleanup(mapistorefolder, username)
except (shutil.Error, OSError) as e:
print "Error during mapistoreCleanup, continuing: %s" % str(e)
try:
ldbCleanup(sambaprivate, username)
except ldb.LdbError as e:
print "Error during ldbCleanup, continuing: %s" % str(e)
try:
sqlCleanup(username)
except Exception as e:
print "Error during sqlCleanup, continuing: %s" % str(e)
def getsep(client):
seq = None
(code, data) = client.list("", "")
if code == "OK" and data is not None:
# yes this is ugly but it works on cyrus and dovecot.
# (\\Noinferiors \\HasNoChildren) "/" INBOX
m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
sep = m.group(1)
return sep
def extractmb(si):
inparen = False
inquote = False
part = []
parts = []
for char in si:
if inparen:
if char == ")":
inparen = False
parts.append("".join(part))
else:
part.append(char)
elif inquote:
if char == "\"":
inquote = False
parts.append("".join(part))
else:
part.append(char)
else:
if char == "(":
inparen = True
elif char == "\"":
inquote = True
elif char == " ":
part = []
else:
part.append(char)
return parts[-1]
def cleanupmb(mb, sep, client):
(code, data) = client.list("%s%s" % (mb, sep), "%")
if code == "OK":
for si in data:
if si is not None:
submb = extractmb(si)
cleanupmb(submb, sep, client)
else:
raise Exception, "Failure while cleaning up '%s'" % mb
client.unsubscribe(mb)
(code, data) = client.delete(mb)
if code == "OK":
print "mailbox '%s' deleted" % mb
else:
print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
def imapCleanup(imaphost, imapport, username, userpass):
print "Starting IMAP cleanup"
client = imaplib.IMAP4(imaphost, imapport)
(code, data) = client.login(username, userpass)
if code != "OK":
raise Exception, "Login failure"
print "Logged in as '%s'" % username
sep = getsep(client)
if not sep:
client.logout()
return
for foldername in ("Sync Issues", "Junk E-mail",
"INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
"Probl&AOg-mes de synchronisation"):
(code, data) = client.list(foldername, "%")
if code == "OK":
for si in data:
if si is not None:
mb = extractmb(si)
cleanupmb(mb, sep, client)
client.logout()
def mapistoreCleanup(mapistorefolder, username):
print "Starting MAPIstore cleanup"
# delete the user's folder under the mapistore and under mapistore/SOGo
mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
for f in filenames:
if f != "password":
os.unlink("%s/%s" % (dirpath,f))
break #one level only
shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)
def ldbCleanup(sambaprivate, username):
conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
# find the DN of the user
entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
if not entries:
print "cn = %s not found in openchange.ldb" %(username)
return
for entry in entries:
# search again, but with the user's DN as a base
subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
for subentry in subentries:
print "Deleting %s" % (subentry.dn)
conn.delete(subentry.dn)
def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
try:
import MySQLdb
except ImportError:
msg ="""The python 'MySQLdb' module is not available
On Debian based distro, install it using 'apt-get install python-mysqlbd'
On RHEL, install it using 'yum install MySQL-python'"""
raise Exception(msg)
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
c=conn.cursor()
tablename="sogo_cache_folder_%s" % (username)
c.execute("TRUNCATE TABLE %s" % tablename)
print "Table %s emptied" % tablename
def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
try:
import pg
except ImportError:
msg ="""The python 'pg' module is not available
On Debian based distro, install it using 'apt-get install python-pygresql'
On RHEL, install it using 'yum install python-pgsql'"""
raise Exception(msg)
conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname)
tablename = "sogo_cache_folder_%s" % username
conn.query("DELETE FROM %s" % tablename)
print "Table '%s' emptied" % tablename
def getOCSFolderInfoURL():
global sogoSysDefaultsFile, sogoUserDefaultsFile
OCSFolderInfoURL = ""
# read defaults from defaults files
# order is important, user defaults must have precedence
for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
if os.path.exists(f):
# FIXME: this is ugly, we should have a python plist parser
# plistlib only supports XML plists.
# the following magic replaces this shell pipeline:
# sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
tmp = p2.communicate()[0]
if tmp: OCSFolderInfoURL = tmp
return OCSFolderInfoURL
def asCSSIdentifier(inputString):
cssEscapingCharMap = {"_" : "_U_",
"." : "_D_",
"#" : "_H_",
"@" : "_A_",
"*" : "_S_",
":" : "_C_",
"," : "_CO_",
" " : "_SP_",
"'" : "_SQ_",
"&" : "_AM_",
"+" : "_P_"}
newChars = []
for c in inputString:
if c in cssEscapingCharMap:
newChars.append(cssEscapingCharMap[c])
else:
newChars.append(c)
return "".join(newChars)
def sqlCleanup(username):
print "Starting SQL cleanup"
OCSFolderInfoURL = getOCSFolderInfoURL()
if OCSFolderInfoURL is None:
raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
# postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)
if not m:
raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)
proto = m.group(1)
dbuser = m.group(2)
dbpass = m.group(3)
dbhost = m.group(4)
dbport = m.group(5)
dbname = m.group(6)
# 7 is folderinfo table
encodedUserName = asCSSIdentifier(username)
if (proto == "postgresql"):
postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
elif (proto == "mysql"):
mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
else:
raise Exception("Unknown sql proto: %s" % (proto))
if __name__ == "__main__":
main()
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+5 -3
View File
@@ -53,7 +53,8 @@ def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=(
if found:
if DEBUG: print "\t", '[%s] FOUND -- "%s"' % found.groups()
else:
notfound.append("-->\t[%s] ==== Not Found ====" % value)
#notfound.append("-->\t[%s] ==== Not Found ====" % value)
notfound.append("""-->\t"%s" = "%s";""" % (value, value))
if notfound:
if not DEBUG:print pathname
print "\n".join(notfound)
@@ -69,10 +70,11 @@ def main():
print 'Usage:', sys.argv[0], '[-g]\n\t\t-g: debug will show matching also'
sys.exit(1)
greylist = ('UIxFilterEditor.wox')
#greylist = ('UIxFilterEditor.wox')
greylist = ()
#- Get only the label:value from all lines
recomp = re.compile(' label:[^=]*="(.*?)"')
recomp = re.compile('.label:[^=]*="(.*?)"')
find_missing_translations('../UI', 'wox', recomp, greylist)
#- [self labelForKey: @"Issuer"]
@@ -6,7 +6,6 @@ vevent_class2 = "(حدث سري)";
vtodo_class0 = "(مهمة عامة)";
vtodo_class1 = "(مهمة خاصة)";
vtodo_class2 = "(مهمة سرية)";
/* Receipts */
"The event \"%{Summary}\" was created" = "أُنشِئ الحدث \"%{Summary}\"";
"The event \"%{Summary}\" was deleted" = "حُذِفَ الحدث \"%{Summary}\"";
@@ -14,7 +13,6 @@ vtodo_class2 = "(مهمة سرية)";
"The following attendees(s) were notified" = "تم ابلاغ المدعو (المدعويين) الأتي أسماءهم";
"The following attendees(s) were added" = "تم أضافة المدعو (المدعويين) الأتي أسماءهم";
"The following attendees(s) were removed" = "تم مسح المدعو (المدعويين) الأتي أسماءهم";
/* IMIP messages */
"calendar_label" = "التقويم";
"startDate_label" = "البداية";
@@ -23,20 +21,17 @@ vtodo_class2 = "(مهمة سرية)";
"location_label" = "المكان";
"summary_label" = "الملخص:";
"comment_label" = "التعليق:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "دعوة الحدث: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(أرسلت بواسطة٪ {SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "وقد دعت%{Organizer} %{SentByText} الى %{Summary} . ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate}⏎\nالوصف:%{Description}\n";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}قام بدعوتك %{Summary}. ⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}\n";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "الحدث الغي : \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "ألغى%{Organizer} %{SentByText} هذا الحدث:.%{Summary} ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate} ⏎\nالوصف:%{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}قام بإلغاء هذا الحدث: %{Summary}.⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "موعد \"%{Summary}\" بتاريخ %{OldStartDate} قد تغير";
@@ -46,7 +41,6 @@ vtodo_class2 = "(مهمة سرية)";
= " لقد تغيرت المحددات التالية في الاجتماع \"%{Summary}\":";
"Please accept or decline those changes."
= "يرجى قبول أو رفض تلك التغييرات.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "دعوةٌ مقبولة: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "دعوةٌ مرفوضة: \"%{Summary}\"";
@@ -60,7 +54,6 @@ vtodo_class2 = "(مهمة سرية)";
= "وقد فوض%{Attendee} %{SentByText} الدعوة إلى %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} لم يقرر حتى الآن حضورهذا الحدث الخاص بك.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "لا يمكن الوصول إلى الموارد: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "الحد الأقصى لعدد الحجوزات في وقت واحد (%{NumberOfSimultaneousBookings}) وصلت للموارد \"%{Cn} %{SystemEmail}\" . الحدث المتضارب في الموعد هو \"٪ {EventTitle}\"، ويبدأ فى٪ {StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Isilpeko ekitaldia)";
vtodo_class0 = "(Zeregin publikoa)";
vtodo_class1 = "(Zeregin pribatua)";
vtodo_class2 = "(Isilpeko egitekoa)";
/* Receipts */
"The event \"%{Summary}\" was created" = "\"%{Summary}\" ekitaldia sortu da";
"The event \"%{Summary}\" was deleted" = "\"%{Summary}\" ekitaldia ezabatu da";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
"The following attendees(s) were notified" = "Ondorengo partaidea(k) jakinarazi d(ir)a";
"The following attendees(s) were added" = "Ondorengo partaidea(k) gehitu d(ir)a";
"The following attendees(s) were removed" = "Ondorengo partaidea(k) ezabatu d(ir)a";
/* IMIP messages */
"calendar_label" = "Egutegia";
"startDate_label" = "Hasi";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Isilpeko egitekoa)";
"location_label" = "Kokapena";
"summary_label" = "Laburpena:";
"comment_label" = "Iruzkina:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Ekitaldirako gonbidapena: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy}-k bidalia) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}.-ra\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDeskribapena: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}-ra \n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nEnd: %{EndDate}-an, %{EndTime}-tan\nDeskribapena: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Ekitaldia bertan behera utzi da: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} ekitaldi hau bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDEskribapena: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}-k honako ekitaldia bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nAmaiera: %{EndDate}-an %{EndTime}-tan\nDeskribapena: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "%{OldStartDate} eguneko \"%{Summary}\" hitzordua aldatu da";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
= " \"%{Summary}\" bilerako ondorengo parametroak aldatu dira:";
"Please accept or decline those changes."
= "Mesedez, onartu edo ezetsi ondorengo aldaketak.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Gonbidapena onartua: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Ezetzitako gonbidapena: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
= "%{Attendee} %{SentByText}-k zure gonbidapena %{Delegate}-ri delegatu dio.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}-k ez du oraindik zure gonbidapenari buruz erabakirik hartu.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ezin da honako baliabidea atzitu: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" baliabidearentzako gehienezko erreserba kopurura (%{NumberOfSimultaneousBookings}) iritsi gara. Gatazka sortu duen ekitaldia \"%{EventTitle}\" da, eta %{StartDate}-n hasten da.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Evento Confidencial)";
vtodo_class0 = "(Tarefa Pública)";
vtodo_class1 = "(Tarefa Privada)";
vtodo_class2 = "(Tarefa Confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "O evento \"%{Summary}\" foi criado";
"The event \"%{Summary}\" was deleted" = "O evento \"%{Summary}\" foi removido";
@@ -15,29 +14,25 @@ vtodo_class2 = "(Tarefa Confidencial)";
"The following attendees(s) were notified" = "Estes participantes foram notificados";
"The following attendees(s) were added" = "Estes participantes foram adicionados";
"The following attendees(s) were removed" = "Estes participantes foram removidos";
/* IMIP messages */
"calendar_label" = "Calendário";
"startDate_label" = "Início";
"endDate_label" = "Fim";
"due_label" = "Data de Vencimento:";
"location_label" = "Local";
"location_label" = "Localização";
"summary_label" = "Resumo:";
"comment_label" = "Comentário:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Convite do Evento: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviado por %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidou você para %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} convidou você para %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Evento Cancelado: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}cancelou este evento: %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} cancelou este evento: %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "O compromisso \"%{Summary}\" de %{OldStartDate} mudou";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
= "Os seguintes parâmetros mudaram na reunião \"%{Summary}\" :\n\n";
"Please accept or decline those changes."
= "Por favor, aceitar ou recusar as alterações.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Convite aceito: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Convite recusado: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
= "%{Attendee} %{SentByText} delegou o convite para %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}foi ainda não decidiu seu convite ao evento.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Não foi possível acessar o recurso: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "O número máximo de reservas simultaneas (%{NumberOfSimultaneousBookings}) acabou para o recurso \"%{Cn} %{SystemEmail}\". O evento conflitante é \"%{EventTitle}\", e inicia em %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Esdeveniment confidencial)";
vtodo_class0 = "(Tasca pública)";
vtodo_class1 = "(Tasca privada)";
vtodo_class2 = "(Tasca confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Es va crear l'esdeveniment \"%{Summary}\" ";
"The event \"%{Summary}\" was deleted" = "Es va esborrar l'esdeveniment \"%{Summary}\" ";
"The event \"%{Summary}\" was updated" = "Es va modificar l'esdeveniment \"%{Summary}\" ";
"The following attendees(s) were notified" = "I va notificar als següents participants ";
"The following attendees(s) were added" = "Es van agregar els següents participants ";
"The following attendees(s) were added" = "S'han afegit els següents participants";
"The following attendees(s) were removed" = "Heu suprimit els assistents següents";
/* IMIP messages */
"calendar_label" = "Calendari";
"startDate_label" = "Inici";
"endDate_label" = "Final";
"due_label" = "Data límit:";
"due_label" = "Data de venciment";
"location_label" = "Lloc";
"summary_label" = "Resum:";
"comment_label" = "Comentari:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invitació a l'esdeveniment: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviada per %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Esdeveniment suspès: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "La cita \"%{Summary}\" per al dia %{OldStartDate} ha canviat";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tasca confidencial)";
= "Els paràmetres següents han canviat en la reunió \"%{Summary}\":";
"Please accept or decline those changes."
= "Si us plau, accepteu o rebutgeu aquests canvis.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Invitació acceptada: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Invitació rebutjada; \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tasca confidencial)";
= "%{Attendee} %{SentByText}ha delegat la invitació en %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}encara no s'ha decidit sobre la invitació a l'esdeveniment.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "No es pot accedir al recurs: \"%{Cn} %{SystemEmail}\" ";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Nombre màxim de reserves simultànies (%{NumberOfSimultaneousBookings}) assolit per al recurs \"%{Cn} %{SystemEmail}\". L'esdeveniment que entra en conflicte és \"%{EventTitle}\", i comença el %{StartDate}.";
Loaded 100 of 922 files, more files were not shown because too many files have changed in this diff. Show more