Whats the simplest way to find for a group of user all the group membership? - lotus-notes

I do have one domino group (Access Control List only), lets call them Main_Group.
This group includes all employees, that I want to know on which other domino groups they are member of.
Members of Main_Group:
- John Smith/ORGANIZATION
- Peter Smith/ORGANIZATION
- Jeff Smith/ORGANIZATION
Of course this list is much longer then these 3 entries.
I would look for each member in this group, in which other domino group this user is member and put this information into a CSV. The CSV should have a format like this:
UserName;DominoGroups
John Smith;Domino_Group1,Domino_Group2,Domino_Group3
Peter Smith;Domino_Group2
Jeff Smith;Domino_Group1,Domino_Group3
Whats the best way to achieve to this information? Lotus Script, any View with formula? Or is there already a notes database is doing this?

It's not simple. A person can be in a group through one or more levels of indirection. I.e., a person is in GroupA and GroupA is in GroupB, and GroupB is in GroupC, and GroupC is in GroupD, and by the way GroupE, GroupF, and GroupG... You will have to write code to recursively traverse groups, detect cycles, and come up with a definitive list of group memberships. As far as I know, there's never been an API exposed for this.

There is no simple way to get what you want. You could create a view in the adressbook, use the "Group"- view as template and add one categorized column for the item "Members". Unfortunately -as Richard wrote- you will not get nested group memberships like that.
You would need to:
Cycle through all group documents
recursively get all members for every group
whenever your user is in the members, then add the group name to a list / an array...
export the result
BUT: If you just need to know / see what groups a specific user is member of, then use the Domino Administrator Client. Open the "Groups" View, then the "Groups" pane and select "Manage groups". Then select the user in the leftmost panel and click on "Member hierarchie" on the right side, then you see the groups that this user is member of, even nested ones. Unfortunately you cannot export this information.

This code builds a list of dynamic arrays to act as key/value pairs (where each value is an array). It's built from the Group view in names.nsf. Rather than taking each group name and loading up the members, it builds it the other way round so for each member, it has an array of groups. Groups can be in other groups so it runs through each group recursively. In order to prevent loops (e.g. where group A is in group B and vice-versa) it uses the visited array which terminates that part of the search if the group has already been visited. The visited array ends up, upon completion of the recursion, the list of groups the user is in.
Building the key/value List initially would be quicker than multiple full text searches, especially if, rather than looking up one name, you're looping all user names in names.nsf as once the key/value list is built there's no need to query the database again. I've not built the loop for each user but it could be added quite easily to the getGroupsForUser function.
Code below
getGroupsForUser function. Returns a formatted string of each group that a user is in. The user name is the first item.
Function getGroupsForUser(userName As String) As String
If userName="" Then Exit function
Dim ns As New NotesSession, namesDatabase As NotesDatabase
Dim visited As Variant, groupKeyValueStore List As Variant
Dim returnString As String, separator As String, i As Integer
Set namesDatabase = ns.getDatabase(ns.Currentdatabase.Server, "names.nsf", False)
visited = Null
Call getGroupKeyValues(groupKeyValueStore, namesDatabase)
Call searchGroupsRecursive(userName, visited, groupKeyValueStore)
i=0
returnString = ""
ForAll item In visited
If i=0 Then separator = ""
If i=1 Then separator = ";"
If i>1 Then separator = ","
returnString = returnString + separator + item
i = i + 1
End forall
getGroupsForUser = returnString
End Function
getGroupKeyValues loops through the Groups view in names.nsf and creates the key/value List.
Public Function getGroupKeyValues(groupKeyValueStore List As Variant , namesDatabase As NotesDatabase)
Dim groupView As NotesView, doc As NotesDocument, members As Variant, groupName As String
Dim separator As String, values As Variant, i As Integer, tempString(0) As String
Set groupView = namesDatabase.getView("Groups")
Set doc=groupView.Getfirstdocument()
Do Until doc Is Nothing
groupName = doc.ListName(0)
members = doc.getItemValue("Members")
ForAll member In members
If IsElement(groupKeyValueStore(member)) Then
If IsNull(ArrayGetIndex(groupKeyValueStore(member), groupName)) Then
values = groupKeyValueStore(member)
i = ubound(values) + 1
ReDim Preserve values(i)
values(i) = groupName
groupKeyValueStore(member) = values
End If
Else
tempString(0) = groupName
groupKeyValueStore(member) = tempString
End If
End ForAll
Set doc=groupView.getNextDocument(doc)
Loop
End Function
searchGroupsRecursive recursively searches each group, ensuring no group is visited twice.
Public Function searchGroupsRecursive(userName As String, visited As Variant, groupKeyValueStore List As Variant) As Variant
Dim length As Integer, userNotesName As NotesName, fullUserName As String
Dim tempArray(0) As String
Set userNotesName = New NotesName(userName)
fullUserName = userNotesName.Canonical
If IsNull(visited) Then
tempArray(0) = userName
visited = tempArray
Else
length = UBound(visited)
ReDim Preserve visited(length + 1)
visited(length + 1) = userName
End If
If Not isElement(groupKeyValueStore(fullUserName)) Then Exit function
ForAll item In groupKeyValueStore(fullUserName)
Call searchGroupsRecursive(CStr(item), visited, groupKeyValueStore)
End ForAll
End Function

Related

What sets limits on maximum Collection size/count in VBA?

I am using a nested Collection to store validation data from Excel, of the form: coll1(case)(subtype)(item).
My code is essentially looping through a flat input list and bin sorting the contents into collections - the top level collection is a collection of validation data for all data sources (cases), the second tier is a type collection (within each case, there are different possible types/classes of data) and the final tier is the valid list of tags/labels for things of that particular class/type.
In the code below, inp is read in from a vertical stack of Excel cells, but is essentially a list of (unique) Strings of the form "\validation_data_class\case\type\label" - hence the Split() into the labels() array to then parse.
Public tag_data As Collection
Private Sub load_tags(inp As Variant)
Dim i As Long, label() As String
Dim case_name As String, type_name As String, tag_name As String
Dim tmp_coll As Collection, tmp_coll2 As Collection
Set tag_data = New Collection
For i = LBound(inp) To UBound(inp) ' Check this works if only one entry in the list - may need IsArray() check
label = Split(inp(i, 1), "\")
Select Case label(1)
Case "tag"
' Extract the case name from the label and get its number, so we can store data in the right element of tag_data()
case_name = label(2): If Not KeyExists(tag_data, case_name) Then Set tmp_coll = New Collection: tag_data.Add tmp_coll, case_name
' Extract the type name from the label and store it, if needed
type_name = label(3): Set tmp_coll = tag_data(case_name)
If Not KeyExists(tmp_coll, type_name) Then Set tmp_coll2 = New Collection: tmp_coll.Add tmp_coll2, type_name
' Extract the actual tag and store it in the list (assumes we have ensured no duplicates already)
tag_name = label(4): Set tmp_coll = tag_data(case_name)(type_name)
Debug.Assert i < 719
tmp_coll.Add tag_name, tag_name
Case "prop"
' Still to implement
End Select
Next i
End Sub
Function KeyExists(coll As Collection, key As String) As Boolean
On Error GoTo ErrHandler
IsObject (coll.Item(key))
KeyExists = True
Exit Function
ErrHandler:
' Do nothing
End Function
The problem I am having is that it gets as far as my Debug.Assert line and then silently fails on that 719th addition to the lowest-level Collection. Weirdly it will run if I don't use keys for the lowest-level Collection, which then allows me to add the final 2 items (in this particular case, I need 721 items in that Collection, but it could be more or less in other scenarios).
I will take the workaround of not using Keys for that large Collection if I need to, but it makes my actual validation against this set of lists that bit harder later on, because I cannot just use the KeyExists method, but will have to write a slower function to crawl the un-labelled Collection looking for a match.

How to prevent some users to open a document from a Lotus view?

I have:
a Lotus view which holds lots of documents,
a field X on every document, which holds users, who should be able to open this document.
How can I prvent opening a particular document for all users except those listed in filed X?
I guess this could be done through QueryOpenDocument event, but would need some assistance on how to construct the code.
Thanks.
QueryOpenDocument, view selection formula and other things like that are not a good way to prevent users from opening your document as a security measure.
A user may create a personal view or copy your database and/or documents to another place (flash usb, etc).
Security measures should involve:
Encryption (via public and/or secret encryption keys)
Readers access, via Readers field (a special field, which content is usernames of authorized users, who can read this document).
In some cases you may need to prevent opening a document from view, not for security reasons, but just to solve a particular task.
For instance, if your view shows documents which should not be opened with any form.
In this case, open view in designer, go to QueryOpenDocument and add some code like that:
Sub Queryopendocument(Source As Notesuiview, Continue As Variant)
Dim doc2BeOpened As NotesDocument
Set doc2BeOpened = Source.Documents.getFirstDocument()
'checking MyField value to decide, open or not open the document
If doc2BeOpened.MyField(0) = "some value" Then
continue = False 'preventing `open document` action
End If
End Sub
To allow opening a document make sure that Continue variable is equal to True. It is True by default.
UPDATE №1
If your document contains a list of common user names, then you can use the following code:
Sub Queryopendocument(Source As Notesuiview, Continue As Variant)
Dim session As New NotesSession
Dim userName As String
Dim whoAllowed2ViewDoc As Variant
Dim doc2BeOpened As NotesDocument
userName = session.CommonUserName
Set doc2BeOpened = Source.Documents.getFirstDocument()
'let say your field name is AllowedPeopleNames
'you can use doc2BeOpened.AllowedPeopleNames or doc2BeOpened.GetItemValue("AllowedPeopleNames")
'both (without (0) at the end, will return you a variant array with names)
whoAllowed2ViewDoc = doc2BeOpened.GetItemValue("AllowedPeopleNames")
If Not Isnumeric(Arraygetindex(whoAllowed2ViewDoc, userName)) Then
continue = False 'preventing `open document` action if userName is not listed in whoAllowed2ViewDoc array
End If
End Sub
Please note, that the code above works for common user names.
John Smith - is a common username
John Smith\IT\Acme - is abbreviated username
CN=John Smith\OU=IT\O=Acme - is fully qualified name (canonical form).
To get fully qualified name (also named as canonical form), you can use session.username
To get abbreviated form of name, get fully qualified username, then use NotesName class, built with fully qualified name as a parameter and then get an abbreviated form of name from it.
Dim notesName as NotesName
Dim abbreviatedUserName as String
Set notesName = New NotesName( fullyQualifiedName )
abbreviatedUserName = notesName.Abbreviated
UPDATE №2
Be careful when checking username presence in the array. The first thing you need to determine what form is using for names, stored in that (field) array AllowedPeopleNames.
If names in this array looks like:
"John Smith"
"Luis Brown"
"Antony Stoppard"
then this array contains common usernames, and you need to get username for checking like that:
Dim session as New NotesSession
Dim username as String
username = session.CommonUserName
If names in this array looks like:
"John Smith/IT/Acme"
"Luis Brown/IT/Acme"
"Antony Stoppard/IT/Acme"
then this array contains names in abbreviated form. And you need to get username for further checking using this approach:
Dim session as New NotesSession
Dim notesName as New NotesName(session.username)
Dim username as String
username = notesName.abbreviated
And If names in this array looks like:
"CN=John Smith/OU=IT/O=Acme"
"CN=Luis Brown/OU=IT/O=Acme"
"CN=Antony Stoppard/OU=IT/O=Acme"
then it contains names in canonical form. And you should get username for checking with this array using the following approach:
Dim session as New NotesSession
Dim username as String
username = session.username
Hope this helps.

Exporting Business Account attributes with Acumatica API

Our Business Accounts in Acumatica have 13 custom Attributes for our main Business Account Class. I've been able to save values to the Attributes successfully, based on Acumatica's example "Adding Records to the Business Accounts and Opportunities Forms". But I have not been able to figure out how to retrieve the values with an Export.
First, I tried using a format similar to how the field was specified when saving them.
Public Function GetCustomerAttributes(ByVal customerID As String) As String()()
Dim customer As CR303000Content = m_context.CR303000GetSchema()
m_context.CR303000Clear()
Dim idFilter As Filter = New Filter()
idFilter.Field = customer.AccountSummary.BusinessAccount
idFilter.Condition = FilterCondition.Equals
idFilter.Value = customerID
' SIMILAR TO EXAMPLE FOR SAVING
Dim awdField As Field = New Field()
awdField.ObjectName = customer.Attributes.Attribute.ObjectName
awdField.FieldName = "AWD Number"
Dim searchfilters() As Filter = {idFilter}
Dim searchCommands() As Command = {awdField}
Dim searchResult As String()() = m_context.CR303000Export(searchCommands, searchfilters, 0, False, False)
Return searchResult
End Function
I thought this would return one result with the value for our attribute named "AWD Number". Instead, it returned 13 results, one for each attribute, and the value of each one was blank. I changed the FieldName to customer.Attributes.Attribute.FieldName and then it started returning the name of each attribute. So I thought if I added another field for the value, then I might get the name and value in separate results, like this:
Public Function GetCustomerAttributes(ByVal customerID As String) As String()()
Dim customer As CR303000Content = m_context.CR303000GetSchema()
m_context.CR303000Clear()
Dim idFilter As Filter = New Filter()
idFilter.Field = customer.AccountSummary.BusinessAccount
idFilter.Condition = FilterCondition.Equals
idFilter.Value = customerID
Dim awdField As Field = New Field()
awdField.ObjectName = customer.Attributes.Attribute.ObjectName
awdField.FieldName = customer.Attributes.Attribute.FieldName
Dim awdValue As Field = New Field()
awdValue.ObjectName = customer.Attributes.Attribute.ObjectName
awdValue.FieldName = customer.Attributes.Attribute.Value
Dim searchfilters() As Filter = {idFilter}
Dim searchCommands() As Command = {awdField, awdValue}
Dim searchResult As String()() = m_context.CR303000Export(searchCommands, searchfilters, 0, False, False)
Return searchResult
End Function
I did get a 2-item array back for each of the 13 results, but the value in the second field was still blank.
Does anyone know how I can get the values? I don't really care if I have to get them one at a time, but I'd prefer to get them all at once with their names or codes so that I don't have to rely on the indices always staying the same. Below are images of the debugger running on my second example and view in Acumatica. Thanks!
Your first attempt is correct, however you're not using the right object name and field name. The system will dynamically add fields to the primary object (view) of the screen, in this case the object name represented by customer.AccountSummary.BusinessAccount.ObjectName variable (I suggest you use the debugger to see what this value equals too - good learning exercise).
The attribute field name will use the same naming convention as used in How To Retrieve An Attribute Field In StockItems In Acumatica API?. The naming convention is _Attributes. The attribute ID is not the attribute name; I don't see your configuration but I doubt in your case the Attribute ID is "AWD Number". To summarize, the code will look like:
Dim awdField As Field = New Field()
awdField.ObjectName = customer.AccountSummary.BusinessAccount.ObjectName
awdField.FieldName = "AWDNumber_Attributes"
In your example, by putting the Attributes.Attribute.ObjectName object, the system will iterate through all values inside this table, and then return for every row the fields you want. I'm not exactly sure why you're not seeing all the attribute values in this case, but I think you should be fine with the example above.

Get members of a group in Lotus Domino

I retrieve names from a group using formula, and put them in a field of type Names this way:
#Name([CN];NAME)
I want to manipulate this data in my code, but using Lotusscript. Can't find it at Google or Lotus Domino's Help. Is there a way I can handle this?
In LotusScript there is a class named "NotesName" to do such manipulations.
If there is a field named "NAME" in you document, then the code would look like:
Dim doc as NotesDocument
Dim nnName as NotesName
'Somehow get the document, using ws.CurrentDocument.document
'or db.UnprocessedDocments.GetFirstDocument, depends on your situation
Set nnName = New NotesName( doc.GetItemValue("NAME")(0) )
Whatyourlookingfor = nnName.Common
If NAME is a Multivalue then you would have to write a loop to get the common- name for every element in the array doc.GetItemValue("NAME")
The next time you have a question, check out the language cross reference in the help...
There it tells you, what the LotusScript- Pendant for #Name is.
Please try with below suggestion for getting list of person names from group.
First need to check the availability of searching group on names.nsf (All the groups are available on "($VIMGroups)" view.
if the group is available means you need to get the list of values from "Members" item
The members item have variant(list) values. So need to iterate the members for getting each value
Please refer the below sample code:
Set namesDb=session.GetDatabase(db.Server,"names.nsf")
Set groupVw=namesDb.GetView("($VIMGroups)")
Set groupDoc=groupvw.GetDocumentByKey("groupname")
persons= groupDoc.members
Forall person In persons
Msgbox person
End Forall
You can use the Evaluate method. It will return you the result of a Notes Formula:
Dim result as Variant
formula$ = "#Name([CN];NAME)"
result = Evaluate(formula$)
If the formula needs to be evaluated within the context of a document, you can pass that document as a second parameter to the method.
More info here

Creating a Container Property in a VBA Class which returns Indexed Items (Excel VBA 2003)

I started learning VBA for my job at the end of last summer, and I can proudly say this is the first time I haven't be able to find the answer on Google. I started teaching myself about Classes this week, and I have come across a situation where I would like to be able to identify an "indexed property" for my class.
Since that probably isn't the clearest explanation, here is a hypothetical example:
The class which I have created for my super awesome sandwich shop (clsSASS) contains properties for Calories, Weight in Grams, Price, and Ingredients. The first three are variables with very straight forward let and get statements. I.E.:
Public pCal As Integer
Public Property Get Calories() As Integer
Calories= pCal
End Property
Public Property Let Calories(Value As Integer)
pCal = Value
End Property
Ingredients however is designed to contain, in order of entry, the list of ingredients. My initial instinct was to do something like this:
Public pIngd As Collection
Public Property Get Ingredients(Value As Integer) As Collection
Ingredients = pIngd(Value)
End Property
Public Property Set Ingredients(Object As Collection)
Set pIngd = Object
End Property
So if Bacon were the first ingredient in the list (and let's be honest it always would be), something like clsNewSandwich.Ingredients(1) would return the string 'Bacon'.
The problem arose when I added a container property to a class, and then couldn't figure out how to identify the individual items in the container. So this may just be a simple syntax issue that has nothing to do with classes whatsoever.
Many Thanks!
*edited for clarity/continuity
OK - I will retract my advice about always naming let/set and Get the same, since in this case you cannot, since the "input" and "output" types are not the same. So, in the sample below I've named the property which just returns one ingredient as Ingredient
Class "clsSASS":
Dim pIngd As Collection
Property Set Ingredients(c As Collection)
Set pIngd = c
End Property
Property Get Ingredient(v As Integer) As String
Ingredient = pIngd(v)
End Property
Regular module:
Sub Tester()
Dim c As New Collection
Dim s As New clsSASS
c.Add "bacon"
c.Add "lettuce"
c.Add "tomato"
Set s.Ingredients = c
Debug.Print s.Ingredient(1) 'bacon
Debug.Print s.Ingredient(2) 'lettuce
Debug.Print s.Ingredient(3) 'tomato
End Sub

Resources