merge of '0ce8c0fe1af6188f22754102040d63f124d3761b'

and '98ba3d5ee1d111284dfb68999ac43af19ef48122'

Monotone-Parent: 0ce8c0fe1af6188f22754102040d63f124d3761b
Monotone-Parent: 98ba3d5ee1d111284dfb68999ac43af19ef48122
Monotone-Revision: bdac9c079f5f6f8a742d138d4a5f9d41043ae013

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2010-07-26T13:20:23
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle
2010-07-26 13:20:23 +00:00
59 changed files with 1370 additions and 145 deletions
+64
View File
@@ -1,3 +1,66 @@
2010-07-22 Ludovic Marcotte <lmarcotte@inverse.ca>
* Added Migration/Horde/* - scripts used to migrate
the address books and email signatures from Horde to
the SOGo database.
2010-07-21 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/WebServerResources/ContactsUI.js: (dropSelectedContacts):
reenabled copying contacts from system (LDAP) addressbooks.
2010-07-20 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoUserFolder.m (_subFoldersFromFolder:): we
need to validate the current user's access to the listed folders
again because of the recent changes on SOGoParentFolder.
* UI/Contacts/UIxContactFoldersView.m (_subFoldersFromFolder:):
removed unused method.
2010-07-16 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Contacts/UIxContactEditor.m (-photosURL): copy of the method
below.
* UI/Contacts/UIxContactView.m (-photosURL): new access that
returns the URL to the photos contained in the VCARD, whether
inline or not.
(-tabSelection): removed unused method.
* SoObjects/Contacts/SOGoContactGCSEntry.m
(-lookupName:inContext:acquire:): new overriden method to handle
"photoX" lookups and return the corrsponding SOGoContactEntryPhoto
instance, if exists.
* SoObjects/Contacts/SOGoContactEntryPhoto.[hm]: new controller
class for VCARD PHOTO objects.
* SoObjects/SOGo/SOGoContentObject.m (-davContentLength): fixed a
crash occurring with certain versions of GNUstep by using
NSISOLatin1StringEncoding instead of UTF8. This is actually the
correct way of doing things anyway since we want to return the
length in bytes and not in characters.
2010-07-16 Ludovic Marcotte <lmarcotte@inverse.ca>
* UI/Contacts/UIxContactView.m
In secondaryEmail, we make sure that we loop
in all available email address and we pick the
first one that doesn't match the primaryEmail
* UI/Contacts/UIxContactEditor.m
UI/Contacts/UIxContactView.m - we make sure,
when excluding a type, that we reset the
local variable to nil since we might exclude
all values in our array.
2010-07-16 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Appointments/SOGoAppointmentObject.m
(_handleUpdatedEvent:fromOldEvent:): added handling of delegation
chains when resetting the participation status of the attendees.
Fixes #688.
2010-07-15 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoUser.m (-personalCalendarFolderInContext:):
@@ -11,6 +74,7 @@
* UI/Scheduler/UIxCalListingActions.m
(_aptFolder:withClientObject:): removed unused method.
(_fixDates:): apply fix also for monthly views.
* UI/WebServerResources/UIxAppointmentEditor.js
(onComposeToAllAttendees): take the status image DIV into account
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
# Version file
SUBMINOR_VERSION:=2
SUBMINOR_VERSION:=0
# v0.9.32 requires libSOGo v0.9.57
# v0.9.24 requires libWEExtensions v4.5.67
@@ -0,0 +1,84 @@
import PHPDeserializer
import sys
class HordeSignatureConverter:
def __init__(self, user, domain):
self.user = user
self.domain = domain
self.domainLen = len(domain)
def fetchSignatures(self, conn):
self.signatures = None
self.conn = conn
self.fetchIdentities()
return self.signatures
def fetchIdentities(self):
self.users = {}
cursor = self.conn.cursor()
if self.user == "ALL":
userClause = ""
else:
userClause = "AND pref_uid = '%s'" % self.user
query = "SELECT pref_uid, pref_value" \
" FROM horde_prefs" \
" WHERE pref_scope = 'horde'" \
" AND pref_name = 'identities'" \
" %s" % userClause
cursor.execute(query)
self.signatures = {}
records = cursor.fetchall()
max = len(records)
if max > 0:
for record in records:
user = record[0]
signature = self.decodeSignature(record[1], user)
if signature is None or len(signature.strip()) == 0:
print "No useful signature found for %s" % user
else:
self.signatures[user] = signature
print "%d useful signature(s) found in %d record(s)" % (len(self.signatures), max)
else:
print "No record found"
cursor.close()
def decodeSignature(self, prefs, user):
des = PHPDeserializer.PHPDeserializer(prefs)
identities = des.deserialize()
nbrEntries = len(identities)
signatures = []
for identity in identities:
fromAddr = identity["from_addr"]
if (len(fromAddr) > self.domainLen
and fromAddr[-self.domainLen:] == self.domain):
if identity.has_key("signature"):
signatures.append(identity["signature"])
if len(signatures) > 0:
signature = self.chooseSignature(signatures)
else:
signature = None
return signature
def chooseSignature(self, signatures):
biggest = -1
length = -1
count = 0
for signature in signatures:
thisLength = len(signature)
if thisLength > 0 and thisLength > length:
biggest = count
count = count + 1
if biggest == -1:
signature = None
else:
signature = signatures[biggest]
return signature
+135
View File
@@ -0,0 +1,135 @@
# we perform no validation
class PHPDeserializer:
def __init__(self, string):
self.string = string
if string is None:
self.length = 0
else:
self.length = len(string)
self.cursor = 0
def deserializeInteger(self):
start = self.cursor
done = False
while not done:
if self.cursor < self.length:
currentChar = self.string[self.cursor]
if currentChar.isdigit():
self.cursor = self.cursor + 1
else:
done = True
else:
done = True
length = self.cursor - start
if length > 0:
dInteger = int(self.string[start:self.cursor])
else:
dInteger = 0
return dInteger
def deserializeBoolean(self):
start = self.cursor
if self.cursor < self.length:
currentChar = self.string[self.cursor]
if currentChar == "0":
dBoolean = False
else:
dBoolean = True
self.cursor = self.cursor + 1
else:
dBoolean = None
return dBoolean
def deserializeString(self):
length = self.deserializeInteger()
start = self.cursor + 2
end = start+length
value = self.string[start:end]
self.cursor = end + 1
return value
def deserializeArray(self):
isHash = False
max = self.deserializeInteger()
dArray = [ None ] * max
self.cursor = self.cursor + 2
count = 0
while count < max:
elementIndex = self.deserialize()
self.cursor = self.cursor + 1
element = self.deserialize()
if isHash:
if type(elementIndex) == int:
elementIndex = "%d" % elementIndex
else:
if type(elementIndex) != int:
dArray = self._arrayToHash(dArray)
isHash = True
dArray[elementIndex] = element
self.cursor = self.cursor + 1
count = count + 1
if self.string[self.cursor] != "}":
raise Exception, \
("inconsistency detected in serialized string at character %d:\n%s"
% (self.cursor, self._exceptionSample()))
return dArray
def _exceptionSample(self):
if self.cursor > 30:
start = self.cursor - 30
prefix = "..."
else:
start = 0
prefix = ""
carret = self.cursor + len(prefix) - start
length = len(self.string)
if self.cursor + 30 < length:
end = self.cursor + 30
suffix = "..."
else:
end = length
suffix = ""
sample = self.string[start:end]
while sample[0] == " ":
carret = carret - 1
sample = sample[1:]
return "%s%s%s\n%s^" % (prefix, sample, suffix, carret * " ")
def _arrayToHash(self, array):
dHash = {}
count = 0
for element in array:
dHash["%d" % count] = element
count = count + 1
return dHash
def deserialize(self):
dObject = None
if self.string is not None and self.length > 0:
currentChar = self.string[self.cursor]
self.cursor = self.cursor + 2
if currentChar == 'a':
dObject = self.deserializeArray()
elif currentChar == 's':
dObject = self.deserializeString()
elif currentChar == 'i':
dObject = self.deserializeInteger()
elif currentChar == 'b':
dObject = self.deserializeBoolean()
elif currentChar == 'N':
dObject = None
return dObject
+7
View File
@@ -0,0 +1,7 @@
1) copy config.py.in to config.py and personalize the settings
2) personalize the domain setting in signature.py (look for DOMAIN.COM)
3) the scripts require "webdavlib.py", which is located in <SOGoDIR>/Tests/Integration/. Therefore, in order to use the scripts, we must set the environment variable "PYTHONPATH" to that directory on our system.
4) invoke "signature <USER>" for importing signatures
5) invoke "turba <USER>" for importing signatures
6) "<USER>" can have the special "ALL" value
+351
View File
@@ -0,0 +1,351 @@
import PHPDeserializer
import webdavlib
import sys
commonMappings = { "owner_id": "owner",
"object_id": "filename",
"object_uid": "uid",
"object_name": "fn" }
cardMappings = { "object_alias": "nickname",
"object_email": "email",
"object_homeaddress": "homeaddress",
"object_homephone": "homephone",
"object_workaddress": "workaddress",
"object_workphone": "workphone",
"object_cellphone": "cellphone",
"object_fax": "fax",
"object_title": "title",
"object_company": "org",
"object_notes": "notes",
"object_freebusyurl": "fburl" }
prodid = "-//Inverse inc.//SOGo Turba Importer 1.0//EN"
# a managed type of template where each line is put only if at least one field
# has been filled
cardTemplate = u"""BEGIN:VCARD\r
VERSION:3.0\r
PRODID:%s\r
UID:${uid}\r
FN:${fn}\r
TITLE:${title}\r
ORG:${org};\r
NICKNAME:${nickname}\r
EMAIL:${email}\r
ADR;TYPE=work:;;${workaddress};;;;\r
ADR;TYPE=home:;;${homeaddress};;;;\r
TEL;TYPE=work:${workphone}\r
TEL;TYPE=home:${homephone}\r
TEL;TYPE=fax:${fax}\r
NOTE:${notes}\r
FBURL:${fburl}\r
END:VCARD""" % prodid
class TurbaConverter:
def __init__(self, user, webdavConfig):
self.user = user
self.webdavConfig = webdavConfig
def start(self, conn):
self.conn = conn
self.readUsers()
self.missing = []
for user in self.users.keys():
self.hasCards = False
self.hasLists = False
self.currentUser = user
self.readUserRecords()
if self.hasCards or self.hasLists:
print "Converting addressbook of '%s'" % user
self.prepareCards()
self.uploadCards()
self.prepareLists()
self.uploadLists()
else:
self.missing.append(user)
if len(self.missing) > 0:
print "No information extracted for: %s" % ", ".join(self.missing)
print "Done"
def readUsers(self):
self.users = {}
cursor = self.conn.cursor()
query = "SELECT user_uid, datatree_name FROM horde_datatree"
if self.user != "ALL":
query = query + " WHERE user_uid = '%s'" % self.user
cursor.execute(query)
records = cursor.fetchall()
count = 0
max = len(records)
for record in records:
record_user = record[0].lower()
if not self.users.has_key(record_user):
self.users[record_user] = []
self.users[record_user].append(record[1])
count = count + 1
cursor.close()
def readUserRecords(self):
self.cards = {}
self.lists = {}
cursor = self.conn.cursor()
owner_ids = self.users[self.currentUser]
whereClause = "owner_id = '%s'" % "' or owner_id = '".join(owner_ids)
query = "SELECT * FROM turba_objects WHERE %s" % whereClause
cursor.execute(query)
self.prepareColumns(cursor)
records = cursor.fetchall()
count = 0
max = len(records)
while count < max:
self.parseRecord(records[count])
count = count + 1
cursor.close()
def prepareColumns(self, cursor):
self.columns = {}
count = 0
for dbColumn in cursor.description:
columnId = dbColumn[0]
self.columns[columnId] = count
count = count + 1
def parseRecord(self, record):
typeCol = self.columns["object_type"]
meta = {}
self.applyRecordMappings(meta, record, commonMappings)
if record[typeCol] == "Object":
meta["type"] = "card"
self.hasCards = True
self.applyRecordMappings(meta, record, cardMappings)
elif record[typeCol] == "Group":
meta["type"] = "list"
self.hasLists = True
self.fillListMembers(meta, record)
else:
raise Exception, "UNKNOWN TYPE: %s" % record[type]
self.dispatchMeta(meta)
def applyRecordMappings(self, meta, record, mappings):
for k in mappings.keys():
metaKey = mappings[k]
meta[metaKey] = self.recordColumn(record, k)
def recordColumn(self, record, columnName):
columnIndex = self.columns[columnName]
value = record[columnIndex]
if value is None:
value = u""
else:
value = self.deUTF8Ize(value)
return value
def deUTF8Ize(self, value):
# unicode -> repeat(utf-8 str -> iso-8859-1 str) -> unicode
oldValue = value
done = False
while not done:
try:
utf8Value = value.encode("iso-8859-1")
value = utf8Value.decode("utf-8")
except:
done = True
if value == oldValue:
done = True
return value
def fillListMembers(self, meta, record):
members = self.recordColumn(record, "object_members")
if members is not None and len(members) > 0:
deserializer = PHPDeserializer.PHPDeserializer(members)
dMembers = deserializer.deserialize()
else:
dMembers = []
meta["members"] = dMembers
def dispatchMeta(self, meta):
owner = meta["owner"]
if meta["type"] == "card":
repository = self.cards
else:
repository = self.lists
filename = meta["filename"]
repository[filename] = meta
def prepareCards(self):
count = 0
for filename in self.cards.keys():
card = self.cards[filename]
card["data"] = self.buildVCard(card).encode("utf-8")
count = count + 1
if count > 0:
print " prepared %d cards" % count
def buildVCard(self, card):
vcardArray = []
tmplArray = cardTemplate.split("\r\n")
for line in tmplArray:
keyPos = line.find("${")
if keyPos > -1:
keyEndPos = line.find("}")
key = line[keyPos+2:keyEndPos]
if card.has_key(key):
value = card[key]
if len(value) > 0:
newLine = "%s%s%s" % (line[0:keyPos],
value.replace(";", "\;"),
line[keyEndPos + 1:])
vcardArray.append(self.foldLineIfNeeded(newLine))
else:
vcardArray.append(self.foldLineIfNeeded(line))
return "\r\n".join(vcardArray)
def foldLineIfNeeded(self, line):
wasFolded = False
newLine = line\
.replace("\\", "\\\\") \
.replace("\r", "\\r") \
.replace("\n", "\\n")
lines = []
while len(newLine) > 73:
wasFolded = True
lines.append(newLine[0:73])
newLine = newLine[73:]
lines.append(newLine)
newLine = "\r\n ".join(lines)
if wasFolded:
print "line was folded: '%s' ->\n\n%s\n\n" % (line, newLine)
return newLine
def uploadCards(self):
self.uploadEntries(self.cards,
"vcf", "text/x-vcard; charset=utf-8");
def prepareLists(self):
count = 0
skipped = 0
for filename in self.lists.keys():
list = self.lists[filename]
vlist = self.buildVList(list)
if vlist is None:
skipped = skipped + 1
else:
list["data"] = vlist.encode("utf-8")
count = count + 1
if (count + skipped) > 0:
print " prepared %d lists. %d were skipped." % (count, skipped)
def buildVList(self, list):
vlist = None
members = list["members"]
if len(members) > 0:
cardMembers = []
for member in members:
card = self.getListCard(member)
if card is not None:
cardMembers.append(card)
if len(cardMembers) > 0:
vlist = self.assembleVList(list, cardMembers)
else:
print " list '%s' skipped because of lack of usable" \
" members" % list["filename"]
return vlist
def getListCard(self, cardRef):
card = None
if len(cardRef) != 0 and not cardRef.startswith("localldap:"):
if cardRef.startswith("localsql:"):
cardRef = cardRef[9:]
if self.cards.has_key(cardRef):
card = self.cards[cardRef]
else:
print "card reference does not exist: '%s'" % cardRef
return card
def assembleVList(self, list, cardMembers):
entries = []
for cardMember in cardMembers:
if cardMember.has_key("fn") and len(cardMember["fn"]) > 0:
fn = ";FN=%s" % cardMember["fn"]
else:
fn = ""
if cardMember.has_key("email") and len(cardMember["email"]) > 0:
email = ";EMAIL=%s" % cardMember["email"]
else:
email = ""
entries.append("CARD%s%s:%s.vcf"
% (fn, email, cardMember["filename"]))
if list.has_key("fn") and len(list["fn"]) > 0:
listfn = "FN:%s\r\n" % list["fn"]
else:
listfn = ""
vlist = """BEGIN:VLIST\r
PRODID:%s\r
VERSION:1.0\r
UID:%s\r
%s%s\r
END:VLIST""" % (prodid, list["uid"], listfn, "\r\n".join(entries))
return vlist
def uploadLists(self):
self.uploadEntries(self.lists,
"vlf", "text/x-vcard; charset=utf-8");
def uploadEntries(self, entries, extension, mimeType):
isatty = sys.stdout.isatty() # enable progressive display of summary
success = 0
failure = 0
client = webdavlib.WebDAVClient(self.webdavConfig["hostname"],
self.webdavConfig["port"],
self.webdavConfig["username"],
self.webdavConfig["password"])
collection = '/SOGo/dav/%s/Contacts/personal' % self.currentUser
mkcol = webdavlib.WebDAVMKCOL(collection)
client.execute(mkcol)
for entryName in entries.keys():
entry = entries[entryName]
if entry.has_key("data"):
fullFilename = "%s.%s" % (entry["filename"], extension)
url = "%s/%s" % (collection, fullFilename)
put = webdavlib.HTTPPUT(url, entry["data"])
put.content_type = mimeType
client.execute(put)
if (put.response["status"] < 200
or put.response["status"] > 399):
failure = failure + 1
print " error uploading '%s': %d" \
% (fullFilename, put.response["status"])
else:
success = success + 1
if isatty:
print "\r successes: %d; failures: %d" % (success, failure),
if (success + failure) % 5 == 0:
sys.stdout.flush()
if isatty:
print ""
else:
if (success + failure) > 0:
print " successes: %d; failures: %d\n" % (success, failure)
sys.stdout.flush()
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/python
webdavConfig = { "hostname": "sogo.hostname.com",
"port": 80,
"username": "username",
"password": "password" }
dbConfig = { "host": "localhost",
"username": "username",
"password": "password",
"db": "database" }
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/python
import sys
import MySQLdb
import webdavlib
import HordeSignatureConverter
from config import webdavConfig, dbConfig
xmlns_inversedav = "urn:inverse:params:xml:ns:inverse-dav"
def UploadSignature(client, user, signature):
collection = '/SOGo/dav/%s/' % user
proppatch \
= webdavlib.WebDAVPROPPATCH(collection,
{ "{%s}signature" % xmlns_inversedav: \
signature.encode("utf-8") })
client.execute(proppatch)
if (proppatch.response["status"] < 200
or proppatch.response["status"] > 399):
print "Failure uploading signature for user '%s': %d" \
% (user, proppatch.response["status"])
if __name__ == "__main__":
if len(sys.argv) > 1:
user = sys.argv[1]
else:
raise Exception, "<user> argument must be specified" \
" (use 'ALL' for everyone)"
conn = MySQLdb.connect(host = dbConfig["hostname"],
user = dbConfig["username"],
passwd = dbConfig["password"],
db = dbConfig["database"],
use_unicode = True)
cnv = HordeSignatureConverter.HordeSignatureConverter(user, "DOMAIN.COM")
signatures = cnv.fetchSignatures(conn)
conn.close()
client = webdavlib.WebDAVClient(webdavConfig["hostname"],
webdavConfig["port"],
webdavConfig["username"],
webdavConfig["password"])
for user in signatures:
signature = signatures[user]
UploadSignature(client, user, signature)
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/python
import sys
import MySQLdb
import TurbaConverter
from config import webdavConfig, dbConfig
if __name__ == "__main__":
if len(sys.argv) > 1:
user = sys.argv[1]
else:
raise Exception, "<user> argument must be specified" \
" (use 'ALL' for everyone)"
conn = MySQLdb.connect(host = dbConfig["hostname"],
user = dbConfig["username"],
passwd = dbConfig["password"],
db = dbConfig["database"],
use_unicode = True)
cnv = TurbaConverter.TurbaConverter(user, webdavConfig)
cnv.start(conn)
conn.close()
+9 -3
View File
@@ -1,4 +1,4 @@
1.2-2010XXXX (1.2.3)
1.3-20100721 (1.3.0)
--------------------
- added support for the "tentative" status in the invitation responses
- inviting a group of contacts is now possible, where each contact will be
@@ -12,16 +12,22 @@
via the "/SOGo/dav/public" url
- we now provide ICS and XML version of a user's personal calendars when
accessed from his own "Calendar" base collection
- events are now displayed with the coloured stripe representing their
- events are now displayed with the colored stripe representing their
category, if one is defined in the preferences
- fixed display of all-day events in a monthly view where the timezone differs
from the current one
- the event location is now displayed in the calendar view when defined
properly
- added a caching mechanism for freebusy requests, in order to accelerate the
display
- added the ability to specify a time range when requesting a time slot
suggestion
- added live-loading support in the webmail interface with caching support
- updated CKEditor and improved its integration with the current user
language for automatic spell checking support
- added support for displaying photos from contacts
- added a Ukrainian translation
- updated the Czeck translation
- updated the Czech translation
1.2-20100504 (1.2.2)
--------------------
+10
View File
@@ -1,3 +1,13 @@
2010-07-21 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* iCalXMLRenderer.m (_appendPaddingValues:withTag:intoString:):
fixed a typo causing a crash.
2010-07-16 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* NGVCardPhoto.[hm]: new class module that implement facilities
for handling "PHOTO" tags in vcards.
2010-06-08 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* iCalXMLRenderer.m (-[CardGroup xmlRender]): don't append empty
+2
View File
@@ -57,6 +57,7 @@ libNGCards_HEADER_FILES = \
\
NGVCard.h \
NGVList.h \
NGVCardPhoto.h \
NGVCardReference.h \
# NGVCardAddress.h \
# NGVCardStrArrayValue.h \
@@ -110,6 +111,7 @@ libNGCards_OBJC_FILES = \
\
NGVCard.m \
NGVList.m \
NGVCardPhoto.m \
NGVCardReference.m \
NGCardsSaxHandler.m \
# IcalElements.m
+4
View File
@@ -26,6 +26,8 @@
#import "NSArray+NGCards.h"
#import "NGVCardPhoto.h"
#import "NGVCard.h"
@implementation NGVCard
@@ -79,6 +81,8 @@
|| [classTag isEqualToString: @"TITLE"]
|| [classTag isEqualToString: @"VERSION"])
tagClass = [CardElement class];
else if ([classTag isEqualToString: @"PHOTO"])
tagClass = [NGVCardPhoto class];
else
tagClass = [super classForTag: classTag];
+41
View File
@@ -0,0 +1,41 @@
/* NGVCardPhoto.h - this file is part of NGCards
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* NGCards is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* NGCards is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with NGCards; see the file COPYING. If not, write to the
* Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifndef NGVCARDPHOTO_H
#define NGVCARDPHOTO_H
#import "CardElement.h"
@class NSData;
@class NSString;
@interface NGVCardPhoto : CardElement
- (BOOL) isInline;
- (NSString *) type;
- (NSData *) decodedContent;
@end
#endif /* NGVCARDPHOTO_H */
+78
View File
@@ -0,0 +1,78 @@
/* NGVCardPhoto.m - this file is part of NGCards
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* NGCards is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* NGCards is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with NGCards; see the file COPYING. If not, write to the
* Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NSObject+Logs.h>
#import "NGVCardPhoto.h"
@implementation NGVCardPhoto
- (BOOL) isInline
{
return ![[self value: 0 ofAttribute: @"value"] isEqualToString: @"uri"];
}
- (NSString *) type
{
return [[self value: 0 ofAttribute: @"type"] uppercaseString];
}
- (NSData *) decodedContent
{
NSString *encoding, *value;
NSData *decodedContent;
decodedContent = nil;
if ([self isInline])
{
encoding = [[self value: 0 ofAttribute: @"encoding"] uppercaseString];
if ([encoding isEqualToString: @"B"]
|| [encoding isEqualToString: @"BASE64"])
{
/* We bypass -[values:] because we want to obtain the undecoded
value first. */
if ([values count] > 0)
{
value = [values objectAtIndex: 0];
decodedContent = [value dataByDecodingBase64];
}
else
[self errorWithFormat: @"attempt to decode empty value"];
}
else
[self errorWithFormat:
@"decoded content requested with an unknown encoding: '%@'",
encoding];
}
else
[self errorWithFormat:
@"decoded content requested on a PHOTO of type 'uri'"];
return decodedContent;
}
@end
+8 -15
View File
@@ -67,17 +67,14 @@
cmpTag = [aTag uppercaseString];
matchingElements = [NSMutableArray new];
[matchingElements autorelease];
matchingElements = [NSMutableArray arrayWithCapacity: 16];
allElements = [self objectEnumerator];
currentElement = [allElements nextObject];
while (currentElement)
while ((currentElement = [allElements nextObject]))
{
currentTag = [[currentElement tag] uppercaseString];
if ([currentTag isEqualToString: cmpTag])
[matchingElements addObject: currentElement];
currentElement = [allElements nextObject];
}
return matchingElements;
@@ -92,16 +89,12 @@
allElements = [self objectEnumerator];
matchingElements = [NSMutableArray new];
[matchingElements autorelease];
currentElement = [allElements nextObject];
while (currentElement)
{
if ([currentElement hasAttribute: anAttribute
havingValue: aValue])
[matchingElements addObject: currentElement];
currentElement = [allElements nextObject];
}
matchingElements = [NSMutableArray arrayWithCapacity: 16];
while ((currentElement = [allElements nextObject]))
if ([currentElement hasAttribute: anAttribute
havingValue: aValue])
[matchingElements addObject: currentElement];
return matchingElements;
}
+1 -1
View File
@@ -96,7 +96,7 @@
int count;
for (count = 0; count < max; count++)
[rendering appendFormat: @"<%@/>"];
[rendering appendFormat: @"<%@/>", valueTag];
}
- (NSString *) _xmlRenderParameter: (NSString *) paramName
+43 -1
View File
@@ -5638,7 +5638,15 @@ Index: sope-core/NGExtensions/ChangeLog
===================================================================
--- sope-core/NGExtensions/ChangeLog (revision 1664)
+++ sope-core/NGExtensions/ChangeLog (working copy)
@@ -1,3 +1,8 @@
@@ -1,3 +1,16 @@
+2010-07-16 Wolfgang Sourdeau <wsourdeau@inverse.ca>
+
+ * NGBase64Coding.m (-dataByDecodingBase64)
+ (-stringByDecodingBase64, -stringByEncodingBase64): make use of
+ -[NSString lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]
+ rather than -[... cStringLength] to avoid a crash within GNUstep.
+ The latter is deprecated anyway...
+
+2010-01-30 Wolfgang Sourdeau <wsourdeau@inverse.ca>
+
+ * NGRuleEngine.subproj/NGRuleModel.m (-candidateRulesForKey:):
@@ -5647,6 +5655,40 @@ Index: sope-core/NGExtensions/ChangeLog
2009-03-24 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* NGQuotedPrintableCoding.m: encode '_' as '=5F', so that it is not
Index: sope-core/NGExtensions/NGBase64Coding.m
===================================================================
--- sope-core/NGExtensions/NGBase64Coding.m (revision 1664)
+++ sope-core/NGExtensions/NGBase64Coding.m (working copy)
@@ -53,7 +53,7 @@
size_t destLength = -1;
char *dest, *src;
- if ((len = [self cStringLength]) == 0)
+ if ((len = [self lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]) == 0)
return @"";
destSize = (len + 2) / 3 * 4; // 3:4 conversion ratio
@@ -91,7 +91,7 @@
if (StringClass == Nil) StringClass = [NSString class];
- if ((len = [self cStringLength]) == 0)
+ if ((len = [self lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]) == 0)
return @"";
destSize = (len / 4 + 1) * 3 + 1;
@@ -135,9 +135,9 @@
if (StringClass == Nil) StringClass = [NSString class];
- if ((len = [self cStringLength]) == 0)
+ if ((len = [self lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]) == 0)
return [NSData data];
-
+
destSize = (len / 4 + 1) * 3 + 1;
dest = malloc(destSize + 1);
Index: sope-core/NGExtensions/NGRuleEngine.subproj/NGRuleModel.m
===================================================================
--- sope-core/NGExtensions/NGRuleEngine.subproj/NGRuleModel.m (revision 1664)
+26 -26
View File
@@ -412,32 +412,6 @@ static SoSecurityManager *sm = nil;
return componentSet;
}
- (void) reloadWebCalendars: (BOOL) forceReload
{
NSArray *refs;
SOGoWebAppointmentFolder *folder;
SOGoUserSettings *us;
NSDictionary *calSettings;
NSString *ref;
int count, max;
[self _migrateWebCalendarsSettings];
us = [[SOGoUser userWithLogin: owner] userSettings];
calSettings = [us objectForKey: @"Calendar"];
refs = [[calSettings objectForKey: @"WebCalendars"] allKeys];
max = [refs count];
for (count = 0; count < max; count++)
{
ref = [refs objectAtIndex: count];
folder = [SOGoWebAppointmentFolder
folderWithSubscriptionReference: ref
inContainer: self];
if (folder
&& (forceReload || [folder reloadOnLogin]))
[folder loadWebCalendar];
}
}
- (void) _migrateWebCalendarsSettings
{
SOGoUserSettings *us;
@@ -474,6 +448,32 @@ static SoSecurityManager *sm = nil;
[us synchronize];
}
- (void) reloadWebCalendars: (BOOL) forceReload
{
NSArray *refs;
SOGoWebAppointmentFolder *folder;
SOGoUserSettings *us;
NSDictionary *calSettings;
NSString *ref;
int count, max;
[self _migrateWebCalendarsSettings];
us = [[SOGoUser userWithLogin: owner] userSettings];
calSettings = [us objectForKey: @"Calendar"];
refs = [[calSettings objectForKey: @"WebCalendars"] allKeys];
max = [refs count];
for (count = 0; count < max; count++)
{
ref = [refs objectAtIndex: count];
folder = [SOGoWebAppointmentFolder
folderWithSubscriptionReference: ref
inContainer: self];
if (folder
&& (forceReload || [folder reloadOnLogin]))
[folder loadWebCalendar];
}
}
- (NSException *) _fetchPersonalFolders: (NSString *) sql
withChannel: (EOAdaptorChannel *) fc
{
+51 -11
View File
@@ -328,17 +328,56 @@
}
}
- (void) _requireResponseFromAttendees: (NSArray *) attendees
- (void) _removeDelegationChain: (iCalPerson *) delegate
inEvent: (iCalEvent *) event
{
NSEnumerator *enumerator;
iCalPerson *currentAttendee;
NSString *delegatedTo, *mailTo;
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
delegatedTo = [delegate delegatedTo];
if ([delegatedTo length] > 0)
{
mailTo = [delegatedTo rfc822Email];
delegate = [event findAttendeeWithEmail: mailTo];
if (delegate)
{
[self _removeDelegationChain: delegate
inEvent: event];
[event removeFromAttendees: delegate];
}
else
[self errorWithFormat:
@"broken chain: delegate with email '%@' was not found",
mailTo];
}
}
/* This method returns YES when any attendee has been removed and NO
otherwise. */
- (BOOL) _requireResponseFromAttendees: (iCalEvent *) event
{
NSArray *attendees;
iCalPerson *currentAttendee;
BOOL listHasChanged;
int count, max;
attendees = [event attendees];
max = [attendees count];
for (count = 0; count < max; count++)
{
currentAttendee = [attendees objectAtIndex: count];
if ([[currentAttendee delegatedTo] length] > 0)
{
[self _removeDelegationChain: currentAttendee
inEvent: event];
[currentAttendee setDelegatedTo: nil];
listHasChanged = YES;
}
[currentAttendee setRsvp: @"TRUE"];
[currentAttendee setParticipationStatus: iCalPersonPartStatNeedsAction];
}
return listHasChanged;
}
- (void) _handleSequenceUpdateInEvent: (iCalEvent *) newEvent
@@ -400,6 +439,13 @@
iCalEventChanges *changes;
changes = [newEvent getChangesRelativeToEvent: oldEvent];
if ([changes sequenceShouldBeIncreased])
{
// Set new attendees status to "needs action" and recompute changes when
// the list of attendees has changed.
if ([self _requireResponseFromAttendees: newEvent])
changes = [newEvent getChangesRelativeToEvent: oldEvent];
}
attendees = [changes deletedAttendees];
if ([attendees count])
{
@@ -417,8 +463,6 @@
if ([changes sequenceShouldBeIncreased])
{
[newEvent increaseSequence];
// Set new attendees status to "needs action"
[self _requireResponseFromAttendees: [newEvent attendees]];
// Update attendees calendars and send them an update
// notification by email
[self _handleSequenceUpdateInEvent: newEvent
@@ -455,10 +499,6 @@
if ([attendees count])
{
NSArray *originalAttendees;
originalAttendees = [NSArray arrayWithArray: [newEvent attendees]];
// Send an invitation to new attendees
[self _handleAddedUsers: attendees fromEvent: newEvent];
[self sendEMailUsingTemplateNamed: @"Invitation"
+1
View File
@@ -19,6 +19,7 @@ Contacts_OBJC_FILES = \
SOGoContactLDIFEntry.m \
SOGoContactSourceFolder.m \
SOGoUserFolder+Contacts.m \
SOGoContactEntryPhoto.m \
Contacts_RESOURCE_FILES += \
Version \
@@ -0,0 +1,42 @@
/* SOGoContactEntryPhoto.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* 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
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef SOGOCONTACTENTRYPHOTO_H
#define SOGOCONTACTENTRYPHOTO_H
#import <SOGo/SOGoObject.h>
@interface SOGoContactEntryPhoto : SOGoObject
{
int photoID;
}
+ (id) entryPhotoWithID: (int) photoId
inContainer: (id) container;
- (void) setPhotoID: (int) newPhotoID;
- (NSString *) davContentType;
@end
#endif /* SOGOCONTACTENTRYPHOTO_H */
+114
View File
@@ -0,0 +1,114 @@
/* SOGoContactEntryPhoto.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* 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
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOResponse.h>
#import <NGCards/NGVCard.h>
#import <NGCards/NGVCardPhoto.h>
#import "SOGoContactObject.h"
#import "SOGoContactEntryPhoto.h"
@implementation SOGoContactEntryPhoto
+ (id) entryPhotoWithID: (int) photoID
inContainer: (id) container
{
id photo;
photo
= [super objectWithName: [NSString stringWithFormat: @"photo%d", photoID]
inContainer: container];
[photo setPhotoID: photoID];
return photo;
}
- (void) setPhotoID: (int) newPhotoID
{
photoID = newPhotoID;
}
- (NGVCardPhoto *) photo
{
NGVCardPhoto *photo;
NSArray *photoElements;
photoElements = [[container vCard] childrenWithTag: @"photo"];
if ([photoElements count] > photoID)
photo = [photoElements objectAtIndex: photoID];
else
photo = nil;
return photo;
}
- (id) GETAction: (WOContext *) localContext
{
NGVCardPhoto *photo;
NSData *data;
id response;
photo = [self photo];
if ([photo isInline])
data = [photo decodedContent];
else
data = [[photo value: 0] dataUsingEncoding: NSISOLatin1StringEncoding];
if (data)
{
response = [localContext response];
[response setHeader: [self davContentType] forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat:@" %d",
[data length]]
forKey: @"content-length"];
[response setContent: data];
}
else
response = nil;
return response;
}
- (NSString *) davContentType
{
NGVCardPhoto *photo;
NSString *type, *contentType;
photo = [self photo];
if ([photo isInline])
{
type = [[photo type] lowercaseString];
contentType = [NSString stringWithFormat: @"image/%@", type];
}
else
contentType = @"text/plain";
return contentType;
}
@end
+27
View File
@@ -24,6 +24,8 @@
#import <NGCards/NGVCard.h>
#import "SOGoContactEntryPhoto.h"
#import "SOGoContactGCSEntry.h"
@implementation SOGoContactGCSEntry
@@ -62,6 +64,31 @@
/* actions */
- (id) lookupName: (NSString *) lookupName
inContext: (id) localContext
acquire: (BOOL) acquire
{
id obj;
int photoIndex;
NSArray *photoElements;
if ([lookupName hasPrefix: @"photo"])
{
photoElements = [[self vCard] childrenWithTag: @"photo"];
photoIndex = [[lookupName substringFromIndex: 5] intValue];
if (photoIndex > -1 && photoIndex < [photoElements count])
obj = [SOGoContactEntryPhoto entryPhotoWithID: photoIndex
inContainer: self];
else
obj = nil;
}
else
obj = [super lookupName: lookupName inContext: localContext
acquire: acquire];
return obj;
}
- (NSException *) copyToFolder: (SOGoGCSFolder *) newFolder
{
NGVCard *newCard;
+1 -1
View File
@@ -372,7 +372,7 @@
- (NSString *) davContentLength
{
return [NSString stringWithFormat: @"%u",
[content lengthOfBytesUsingEncoding: NSUTF8StringEncoding]];
[content lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]];
}
// - (NSString *) davResourceType
+12 -2
View File
@@ -27,6 +27,7 @@
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoClassSecurityInfo.h>
#import <NGObjWeb/SoSecurityManager.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
@@ -147,18 +148,27 @@
NSMutableArray *folders;
NSEnumerator *subfolders;
SOGoFolder *currentFolder;
NSString *folderName;
NSString *folderName, *folderOwner;
Class subfolderClass;
NSMutableDictionary *currentDictionary;
SoSecurityManager *securityManager;
folders = [NSMutableArray array];
folderOwner = [parentFolder ownerInContext: context];
securityManager = [SoSecurityManager sharedSecurityManager];
subfolderClass = [[parentFolder class] subFolderClass];
subfolders = [[parentFolder subFolders] objectEnumerator];
while ((currentFolder = [subfolders nextObject]))
{
if ([currentFolder isMemberOfClass: subfolderClass])
if (![securityManager validatePermission: SOGoPerm_AccessObject
onObject: currentFolder
inContext: context]
&& [[currentFolder ownerInContext: context]
isEqualToString: folderOwner]
&& [currentFolder isMemberOfClass: subfolderClass])
{
folderName = [NSString stringWithFormat: @"/%@/%@",
[parentFolder nameInContainer],
+1 -1
View File
@@ -1,6 +1,6 @@
/* UIxFolderActions.m - this file is part of SOGo
*
* Copyright (C) 2007 Inverse inc.
* Copyright (C) 2007-2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@@ -2,6 +2,7 @@
"Contact" = "Contact";
"Address" = "Address";
"Photos" = "Photos";
"Other" = "Other";
"Address Books" = "Addressbooks";
@@ -2,6 +2,7 @@
"Contact" = "Kontakt";
"Address" = "Adresa";
"Photos" = "Photos";
"Other" = "Ostatní";
"Address Books" = "Složky kontaktů";
@@ -2,6 +2,7 @@
"Contact" = "Contactpersoon";
"Address" = "Adres";
"Photos" = "Fotos";
"Other" = "Overige";
"Address Books" = "Addressbooks";
@@ -2,6 +2,7 @@
"Contact" = "Contact";
"Address" = "Address";
"Photos" = "Photos";
"Other" = "Other";
"Address Books" = "Address Books";
@@ -2,6 +2,7 @@
"Contact" = "Contact";
"Address" = "Adresses";
"Photos" = "Photos";
"Other" = "Informations complémentaires";
"Address Books" = "Carnet d'adresses";
@@ -2,6 +2,7 @@
"Contact" = "Kontakt";
"Address" = "Adresse";
"Photos" = "Fotos";
"Other" = "Sonstiges";
"Address Books" = "Adressbücher";
@@ -2,6 +2,7 @@
"Contact" = "Contact";
"Address" = "Address";
"Photos" = "Photos";
"Other" = "Other";
"Address Books" = "Addressbooks";
@@ -2,6 +2,7 @@
"Contact" = "Contatto";
"Address" = "Indirizzo";
"Photos" = "Photos";
"Other" = "Altro";
"Address Books" = "Rubrica";
@@ -1,5 +1,10 @@
/* this file is in UTF-8 format! */
"Contact" = "Contact";
"Address" = "Address";
"Photos" = "Photos";
"Other" = "Other";
"Addressbook" = "Адресная книга";
"Addresses" = "Адреса";
"Update" = "Обновить";
@@ -2,6 +2,7 @@
"Contact" = "Contacto";
"Address" = "Dirección";
"Photos" = "Photos";
"Other" = "Otros datos";
"Address Books" = "Libretas de direcciones";
@@ -2,6 +2,7 @@
"Contact" = "Kontakt";
"Address" = "Adress";
"Photos" = "Photos";
"Other" = "Annat";
"Address Books" = "Adressböcker";
+1
View File
@@ -37,6 +37,7 @@
NSString *preferredEmail;
NSString *item;
NGVCard *card;
NSMutableArray *photosURL;
NSMutableDictionary *snapshot; /* contains the values for editing */
SOGoContactFolder *componentAddressBook;
}
+37 -1
View File
@@ -22,6 +22,7 @@
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSEnumerator.h>
#import <NGObjWeb/NSException+HTTP.h>
@@ -33,6 +34,7 @@
#import <NGExtensions/NSNull+misc.h>
#import <NGCards/NGVCard.h>
#import <NGCards/NGVCardPhoto.h>
#import <NGCards/NSArray+NGCards.h>
#import <Contacts/SOGoContactFolder.h>
@@ -51,6 +53,7 @@
{
snapshot = [[NSMutableDictionary alloc] initWithCapacity: 16];
preferredEmail = nil;
photosURL = nil;
}
return self;
@@ -60,6 +63,7 @@
{
[snapshot release];
[preferredEmail release];
[photosURL release];
[super dealloc];
}
@@ -282,6 +286,8 @@
if (![ce hasAttribute: @"type" havingValue: aTypeToExclude])
break;
value = nil;
}
}
@@ -412,7 +418,7 @@
[self _setSnapshotValue: @"telephoneNumber"
to: [self _simpleValueForType: @"work" inArray: elements excluding: @"fax"]];
[self _setSnapshotValue: @"homeTelephoneNumber"
to: [self _simpleValueForType: @"home" inArray: elements excluding: nil]];
to: [self _simpleValueForType: @"home" inArray: elements excluding: @"fax"]];
[self _setSnapshotValue: @"mobile"
to: [self _simpleValueForType: @"cell" inArray: elements excluding: nil]];
[self _setSnapshotValue: @"facsimileTelephoneNumber"
@@ -571,6 +577,36 @@
&& [super canCreateOrModify]);
}
- (NSArray *) photosURL
{
NSArray *photoElements;
NSURL *soURL;
NSString *baseInlineURL, *photoURL;
NGVCardPhoto *photo;
int count, max;
if (!photosURL)
{
soURL = [[self clientObject] soURL];
baseInlineURL = [soURL absoluteString];
photoElements = [card childrenWithTag: @"photo"];
max = [photoElements count];
photosURL = [[NSMutableArray alloc] initWithCapacity: max];
for (count = 0; count < max; count++)
{
photo = [photoElements objectAtIndex: count];
if ([photo isInline])
photoURL = [NSString stringWithFormat: @"%@/photo%d",
baseInlineURL, count];
else
photoURL = [photo value: 0];
[photosURL addObject: photoURL];
}
}
return photosURL;
}
- (CardElement *) _elementWithTag: (NSString *) tag
ofType: (NSString *) type
{
+1 -37
View File
@@ -1,6 +1,6 @@
/* UIxContactFoldersView.m - this file is part of SOGo
*
* Copyright (C) 2006-2009 Inverse inc.
* Copyright (C) 2006-2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@@ -246,42 +246,6 @@
return result;
}
- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder
{
NSMutableArray *folders;
NSEnumerator *subfolders;
SOGoGCSFolder *subfolder;
NSString *folderName;
NSMutableDictionary *currentDictionary;
SoSecurityManager *securityManager;
securityManager = [SoSecurityManager sharedSecurityManager];
folders = [NSMutableArray array];
subfolders = [[parentFolder subFolders] objectEnumerator];
while ((subfolder = [subfolders nextObject]))
{
if (![securityManager validatePermission: SOGoPerm_AccessObject
onObject: subfolder inContext: context])
{
folderName = [NSString stringWithFormat: @"/%@/%@",
[parentFolder nameInContainer],
[subfolder nameInContainer]];
currentDictionary
= [NSMutableDictionary dictionaryWithCapacity: 3];
[currentDictionary setObject: [subfolder displayName]
forKey: @"displayName"];
[currentDictionary setObject: folderName forKey: @"name"];
[currentDictionary setObject: [subfolder folderType]
forKey: @"type"];
[folders addObject: currentDictionary];
}
}
return folders;
}
- (void) checkDefaultModulePreference
{
SOGoUserDefaults *ud;
+1
View File
@@ -32,6 +32,7 @@
NSArray *phones;
CardElement *homeAdr;
CardElement *workAdr;
NSMutableArray *photosURL;
}
- (NSString *) fullName;
+72 -16
View File
@@ -20,30 +20,40 @@
02111-1307, USA.
*/
#import <Foundation/NSURL.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOResponse.h>
#import <NGCards/NGVCard.h>
#import <NGCards/NGVCardPhoto.h>
#import <NGCards/CardElement.h>
#import <NGCards/NSArray+NGCards.h>
#import <NGExtensions/NSString+Ext.h>
#import <SoObjects/Contacts/SOGoContactObject.h>
#import <Contacts/SOGoContactObject.h>
#import "UIxContactView.h"
@implementation UIxContactView
/* accessors */
- (id) init
{
if ((self = [super init]))
{
photosURL = nil;
}
- (NSString *)tabSelection {
NSString *selection;
selection = [self queryParameterForKey:@"tab"];
if (selection == nil)
selection = @"attributes";
return selection;
return self;
}
- (void) dealloc
{
[photosURL release];
[super dealloc];
}
/* accessors */
- (NSString *) _cardStringWithLabel: (NSString *) label
value: (NSString *) value
{
@@ -139,15 +149,29 @@
// We might not have a preferred item but rather something like this:
// EMAIL;TYPE=work:dd@ee.com
// EMAIL;TYPE=home:ff@gg.com
// In this case, we always return the last entry.
//
// or:
//
// EMAIL;TYPE=INTERNET:a@a.com
// EMAIL;TYPE=INTERNET,HOME:b@b.com
//
// In this case, we always return the entry NOT matching the primaryEmail
if ([emails count] > 0)
{
email = [[emails objectAtIndex: [emails count]-1] value: 0];
int i;
if ([email caseInsensitiveCompare: [card preferredEMail]] != NSOrderedSame)
mailTo = [NSString stringWithFormat: @"<a href=\"mailto:%@\""
@" onclick=\"return openMailTo('%@ <%@>');\">"
@"%@</a>", email, [[card fn] stringByReplacingString: @"\"" withString: @""], email, email];
for (i = 0; i < [emails count]; i++)
{
email = [[emails objectAtIndex: i] value: 0];
if ([email caseInsensitiveCompare: [card preferredEMail]] != NSOrderedSame)
{
mailTo = [NSString stringWithFormat: @"<a href=\"mailto:%@\""
@" onclick=\"return openMailTo('%@ <%@>');\">"
@"%@</a>", email, [[card fn] stringByReplacingString: @"\"" withString: @""], email, email];
break;
}
}
}
return [self _cardStringWithLabel: @"Additional Email:"
@@ -214,6 +238,8 @@
if (![ce hasAttribute: @"type" havingValue: aTypeToExclude])
break;
phone = nil;
}
}
@@ -229,7 +255,7 @@
- (NSString *) homePhone
{
return [self _phoneOfType: @"home" withLabel: @"Home:" excluding: nil];
return [self _phoneOfType: @"home" withLabel: @"Home:" excluding: @"fax"];
}
- (NSString *) fax
@@ -614,4 +640,34 @@
return self;
}
- (NSArray *) photosURL
{
NSArray *photoElements;
NSURL *soURL;
NSString *baseInlineURL, *photoURL;
NGVCardPhoto *photo;
int count, max;
if (!photosURL)
{
soURL = [[self clientObject] soURL];
baseInlineURL = [soURL absoluteString];
photoElements = [card childrenWithTag: @"photo"];
max = [photoElements count];
photosURL = [[NSMutableArray alloc] initWithCapacity: max];
for (count = 0; count < max; count++)
{
photo = [photoElements objectAtIndex: count];
if ([photo isInline])
photoURL = [NSString stringWithFormat: @"%@/photo%d",
baseInlineURL, count];
else
photoURL = [photo value: 0];
[photosURL addObject: photoURL];
}
}
return photosURL;
}
@end /* UIxContactView */
+1 -1
View File
@@ -1,6 +1,6 @@
/* UIxContactsUserFolders.m - this file is part of SOGo
*
* Copyright (C) 2007 Inverse inc.
* Copyright (C) 2007-2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@@ -2,6 +2,7 @@
"Contact" = "Контакт";
"Address" = "Адреса";
"Photos" = "Photos";
"Other" = "Інше";
"Address Books" = "Адресні книги";
@@ -1,5 +1,10 @@
/* this file is in UTF-8 format! */
"Contact" = "Contact";
"Address" = "Address";
"Photos" = "Photos";
"Other" = "Other";
"Addressbook" = "Llyfr cyfeiriadau";
"Addresses" = "Cyfeiriadau";
"Update" = "Diweddaru";
@@ -22,7 +22,7 @@ request_info = "vous invite à une réunion.";
"Update status" = "Intégrer les modifications";
Accept = "Accepter";
Decline = "Decliner";
Tentative = "Tentative";
Tentative = "Tentatif";
"Delegate ..." = "Déléguer ...";
"Delegated to" = "Délégué à";
"Update status in calendar" = "Mettre l'agenda à jour";
+1 -1
View File
@@ -1,5 +1,5 @@
# Version file
SUBMINOR_VERSION:=2
SUBMINOR_VERSION:=0
# v0.9.1 requires Main v0.9.59
@@ -318,7 +318,7 @@
/* status type */
"status_" = "Non-spécifié";
"status_NOT-SPECIFIED" = "Non spécifié";
"status_TENTATIVE" = "Tentative";
"status_TENTATIVE" = "Tentatif";
"status_CONFIRMED" = "Confirmé";
"status_CANCELLED" = "Annulé";
"status_NEEDS-ACTION" = "En attente";
@@ -470,7 +470,7 @@ validate_endbeforestart = "La date de fin est avant la date de début.";
"Needs action" = "En attente";
"Accepted" = "Accepté";
"Declined" = "Décliné";
"Tentative" = "Tentative";
"Tentative" = "Tentatif";
"Free" = "Libre";
"Busy" = "Occupé";
+16 -17
View File
@@ -256,23 +256,22 @@ static NSArray *tasksFields = nil;
static NSString *fields[] = { @"startDate", @"c_startdate",
@"endDate", @"c_enddate" };
if (dayBasedView)
for (count = 0; count < 2; count++)
{
aDateField = fields[count * 2];
aDate = [aRecord objectForKey: aDateField];
daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate]
- [userTimeZone secondsFromGMTForDate: startDate]);
if (daylightOffset)
{
aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0
minutes: 0 seconds: daylightOffset];
[aRecord setObject: aDate forKey: aDateField];
aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]];
[aRecord setObject: aDateValue forKey: fields[count * 2 + 1]];
}
}
for (count = 0; count < 2; count++)
{
aDateField = fields[count * 2];
aDate = [aRecord objectForKey: aDateField];
daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate]
- [userTimeZone secondsFromGMTForDate: startDate]);
if (daylightOffset)
{
aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0
minutes: 0 seconds: daylightOffset];
[aRecord setObject: aDate forKey: aDateField];
aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]];
[aRecord setObject: aDateValue forKey: fields[count * 2 + 1]];
}
}
aDateValue = [aRecord objectForKey: @"c_recurrence_id"];
aDate = [aRecord objectForKey: @"cycleStartDate"];
if (aDateValue && aDate)
@@ -29,6 +29,8 @@
</li>
<li target="addressesInfos">
<span><var:string label:value="Address" /></span></li>
<li target="photos">
<span><var:string label:value="Photos" /></span></li>
<li target="otherInfos">
<span><var:string label:value="Other" /></span></li>
</ul>
@@ -332,6 +334,12 @@
</table>
</div>
<div id="photos" class="tab">
<var:foreach list="photosURL" item="currentPhotoURL">
<img var:src="currentPhotoURL" class="contactPhoto"/><br
/></var:foreach>
</div>
<div id="otherInfos" class="tab">
<table class="framenocaption">
<tr>
+4 -2
View File
@@ -18,8 +18,10 @@
/><var:string value="secondaryEmail" escapeHTML="NO"
/><var:string value="screenName" escapeHTML="NO"
/><var:string value="preferredAddress" escapeHTML="NO"
/></div
/><var:foreach list="photosURL" item="currentPhotoURL"
><br/><img var:src="currentPhotoURL" class="contactPhoto"/>
</var:foreach></div
><var:if condition="hasHomeInfos"
><div id="homeInfos"
><h4><var:string label:value="Home" /></h4
+4 -2
View File
@@ -1272,8 +1272,9 @@ function startDragging (itm, e) {
handle.show();
handle.update (count);
if (e.shiftKey || currentFolderIsRemote ())
if (e.shiftKey || currentFolderIsRemote ()) {
handle.addClassName ("copy");
}
}
function whileDragging (itm, e) {
@@ -1311,7 +1312,8 @@ function dropSelectedContacts (action, toId) {
}
}
var fromId = $(selectedFolders[0]).id;
if (!currentFolderIsRemote () || action != "move") {
if ((!currentFolderIsRemote () || action != "move")
&& fromId.substring(1) != toId) {
var url = ApplicationBaseURL + fromId + "/" + action
+ "?folder=" + toId + "&uid="
+ contactIds.join("&uid=");
+2 -1
View File
@@ -448,7 +448,8 @@ function deleteMessageWithDelay(url, id, mailbox, messageId) {
}
function onPrintCurrentMessage(event) {
var rowIds = $("messageList").getSelectedRowsId();
var messageList = $("messageListBody").down("TBODY");
var rowIds = messageList.getSelectedNodes();
if (rowIds.length == 0) {
window.alert(_("Please select a message to print."));
}
+1 -1
View File
@@ -3,4 +3,4 @@
# of the executable.
MAJOR_VERSION=1
MINOR_VERSION=2
MINOR_VERSION=3
+1 -1
View File
@@ -188,7 +188,7 @@ rm -fr ${RPM_BUILD_ROOT}
%config %{_sysconfdir}/httpd/conf.d/SOGo.conf
%config %{_sysconfdir}/sysconfig/sogo
%doc ChangeLog README NEWS Scripts/sql-update-20070724.sh Scripts/sql-update-20070822.sh Scripts/sql-update-20080303.sh Scripts/sql-update-101_to_102.sh Scripts/sql-update-1.2.2_to_1.3.0.sh sql-update-1.2.2_to_1.3.0-mysql.sh
%doc ChangeLog README NEWS Scripts/sql-update-20070724.sh Scripts/sql-update-20070822.sh Scripts/sql-update-20080303.sh Scripts/sql-update-101_to_102.sh Scripts/sql-update-1.2.2_to_1.3.0.sh Scripts/sql-update-1.2.2_to_1.3.0-mysql.sh
%files -n sogo-tool
%{prefix}/Tools/Admin/sogo-tool