OpenNTF.org - Enabling Security Log on Lotus
My Links (Not logged in)
Code Bin Search
 
Hosted by Prominic.NET
Rate This Code
5 - brilliant stuff
4 - very nice
3 - average
2 - needs work
1 - bad
   OpenNTF Code Bin
About This Code
Brief Description:
Enabling Security Log on Lotus Notes documents by capturing it's "PostModeChange" and "Terminate" events. 
Rating:
Not Rated Yet 
Contributor:
Ali Ahmad 
Category:
Application 
Type:
Security 
Document Release:
1.0 
Notes Version:
R6.x 
Last Modified:
27 Apr 2007 
OpenNTF Disclaimer

All of the program code and information presented in the OpenNTF.org Code Bin are provided "as-is", and should be used at your own risk. OpenNTF.org make no express or implied warranty about anything in the Code Bin, and OpenNTF.org will not be responsible or liable for any damage caused by the use or misuse of anything from this site. OpenNTF.org makes no guarantees about anything. Please thoroughly test all of the knowledge and code you find here before you attempt to use them in your production environment.

Code / Description
Log database will be used in synched with the the database at which security log is to be enabled. The Log Profile document would be created for the settings of Log for target database.

Respective database paths must be set in profile/setup document of both of the databases.
The design elements, Script Library, a view and a subfrom in the Log Material Databser would be copied to the target database which has to be enabled for the log.

Usage / Example
Overview : -

This functionality can be used in Lotus Notes based applications to generate a security log for any Lotus Notes Database form. After this functionality is enabled on any database it will generate a comprehensive “Log” of who changed what when in any document of the Lotus Notes. In addition it will also send alret to the selected recipients in case of any unauthorized editing on the specified document. Create log profile /setting document first to enable the log on the specified form.
This functionality is comprised of the following components: -

1. A subform named as “Security”.
2. A Script Library named as “Security”.
3. A hidden view named as “(Security)”.
4. A database where the Log would going to be dumped.

1. Log Profile Database: -

The Log Profile database is used to create the setting document for Log. This database contains a form named “Log Profile”. Use this form to enable the log on the form of any other database. This form contains following major fields: -

i. Database and respective from (one document would be created for one form). The form combo will automatically be populated once the database is selected (but do set the paths of the database in profile document by “Edit Profile” action button from view).
ii. Fields on which log has to be enabled. (Computed)
iii. Persons who are to be intimated in case of any change is done. Please do note that this would not be related in any way with rights of the user in ACL. It will blindly compare the fields data on “QueryModeChange” event and “Terminate” event on the form and if change is found would report a log.
iv. Limited Personal Log means that the person who would be in this field if they would change the value of the fields then Log would not be generated.
v. The embedded view contains child documents comprising the log in RTF field and is “show single categorized” to the ID of the main document.

2. Subform: - (Security)

This subform is embedded in the main form on which security log is to be enabled. Once the subfrom would become the part of main form, its events “PostModeChange” and “Terminate” would call the functions from script library “CaptureModeChange” and “CaptureTerminate” to capture data in the fields on which the Log is enabled through “Log Profile” form in Log database.
The subform contains two fields i.e. “ProfileName” and “LogDocID” to get the handle of Profile document for database path and Log document from Log database respectively.

3. Script Library: - (Security)

This script library actually contains all the logic to: -

i. Capture the data on “PostModeChange” and “Terminate” events.
ii. Compare the data and if found different generates a Log document comprising the complete detail including: -

1. Fld Name
2. New and Old Values
3. User who changed and date, time when he/she changed.
4. Respective document link.

iii. This script lib is also responsible to generate a comprehensive mail alert to the person selected in the respective “Log Profile” document. The mail alert will contain the following: -

1. Log document link.
2. Main profile document link.
3. Source document link.

Lotus Notes Database Synopsis - Generated at 03:47:34 PM on 05/31/2006
Code Library Information
Name: Security
Last Modification: 05/19/2006 05:48:59 PM
LotusScript Code:
Option Public
Private s As NotesSession
Private ws As NotesUIWorkspace
Private db As NotesDatabase
Private logDoc As NotesDocument
Private FldValuesQueryMode As Variant
Private FldVlsBackupTerminate As Variant
Private FldNameArr As Variant
Private totalRowsinTableBefore As Integer
Private totalRowsinTable As Integer
Private totalColsinTable As Integer
Private doc As NotesDocument
Sub Initialize
Set s = New NotesSession
Set ws = New NotesUIWorkspace
Set db= s.CurrentDatabase
End Sub
Sub captureTerminate
Dim pdoc As NotesDocument
Dim uidoc As NotesUIDocument
Dim doc As NotesDocument
Dim logDoc As NotesDocument
Dim numberofRows As Integer
Dim rtNav As NotesRichTextNavigator
Dim body As NotesRichTextItem
Dim logChildDoc As NotesDocument
On Error Goto handler
Set uidoc = ws.CurrentDocument
Set doc = uidoc.Document
Set pdoc= db.GetProfileDocument(doc.ProfileName(0))
'''''''''''''''''''''''''''''''''''' Getting respective Log Document'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim logDb As New NotesDatabase(db.Server,pdoc.LogDBPath(0))
If Not logDB Is Nothing Then
If logDB.IsOpen=False Then
Call logDB.Open(db.Server,pdoc.LogDBPath(0))
End If
Set logDoc = New NotesDocument(logDB)
Set logDoc=logDB.GetDocumentByUNID(doc.LogDocID(0))
If Not logDoc Is Nothing Then
Else
Exit Sub
End If
End If
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Check to see if user have edited the document if not or the document was new doc then
'exit sub
If Isempty(FldValuesQueryMode) Then
Exit Sub
End If
' Taking the backup of the values of log fields into the array and if true then also compare them to
'update the respective backup arrays and populates the array for field names
FldVlsBackupTerminate = getFldValuesBackup(logDoc,doc,True)
'Gettting actual number of rows in case value found changed on the terminate and shrink value arrays
If FldNameArr(0)<>"" Then
numberofRows = Ubound(FldNameArr)+1
Set logChildDoc = New NotesDocument(logDB)
logChildDoc.form="Log Profile_Sub"
logChildDoc.ParentDocumentID = logDoc.UniversalID
Set body = New NotesRichTextItem(logChildDoc,"LogTable")
If body Is Nothing Then
Msgbox "Body not found",64,db.Title
End If
Set rtNav = createTable(numberofRows,body)
If Not rtNav Is Nothing Then
Call printValuesinTable(rtNav,body)
Call logChildDoc.Save(True,True)
If logDoc.MailEnabled(0) = "Yes" Then
Call sendSecurityMail(logDoc,doc,logChildDoc)
End If
End If
End If
handler:
Exit Sub
End Sub
Sub captureModeChange
Dim pdoc As NotesDocument
Dim logDoc As NotesDocument
Dim uidoc As NotesUIDocument
On Error Goto handler
Set uidoc = ws.CurrentDocument
Set doc= uidoc.Document
Set pdoc= db.GetProfileDocument(doc.ProfileName(0))
'''''''''''''''''''''''''''''''''''' Getting respective Log Document'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim logDb As New NotesDatabase(db.Server,pdoc.logDBPath(0))
If Not logDB Is Nothing Then
If logDB.IsOpen=False Then
Call logDB.Open(db.Server,pdoc.LogDBPath(0))
End If
Set logDoc = New NotesDocument(logDB)
Set logDoc=logDB.GetDocumentByUNID(doc.LogDocID(0))
End If
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''' Getting total number of rows depending upong the log fields if log doc is available''''''''''''''''''''''''''''''''''''''''''
If uidoc.IsNewDoc Then
Exit Sub
End If
' Taking the values backup of log fields into the array and if true then also compare them to
'update the respective backup arrays and populates the array for field names
FldValuesQueryMode = getFldValuesBackup(logDoc,doc,False)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
handler:
Exit Sub
End Sub
Function getFldValuesBackup(logDoc As NotesDocument,srcDoc As NotesDocument,compare As Boolean) As Variant
Dim valueString() As Variant
Redim valueString(0) As Variant
Dim tempValString As String
Dim tempValStringNew() As Variant
Redim tempValStringNew(0) As Variant
Dim tempValFldNameArr() As Variant
Redim tempValFldNameArr(0) As Variant
If compare=False Then
Forall vls In logDoc.LogFields
Redim Preserve valueString(i%) As Variant
valueString(i%) = getFldValueString(vls,srcDoc)
i%=i%+1
End Forall
Else
Forall vls In logDoc.LogFields
tempValString = getFldValueString(vls,srcDoc)
If tempValString <> "<Item not available for Log>" Then
If tempValString <> FldValuesQueryMode(a%) Then
Redim Preserve valueString(i%) As Variant
valueString(i%) = tempValString
Redim Preserve tempValStringNew(i%) As Variant
tempValStringNew(i%) = FldValuesQueryMode(a%)
Redim Preserve tempValFldNameArr(i%) As Variant
tempValFldNameArr(i%) = vls
i%=i%+1
End If
Else
Redim Preserve valueString(i%) As Variant
valueString(i%) = tempValString
Redim Preserve tempvalStringNew(i%) As Variant
tempvalStringNew(i%) = FldValuesQueryMode(a%)
Redim Preserve tempValFldNameArr(i%) As Variant
tempValFldNameArr(i%) = vls
i%=i%+1
End If
a%=a%+1
End Forall
FldValuesQueryMode=tempValStringNew
FldNameArr = tempValFldNameArr
End If
getFldValuesBackup=valueString
End Function
Function getFldValueString(fldName As String,document As NotesDocument) As String
Dim item As NotesItem
Dim vlString As String
Set item = document.GetFirstItem(fldName)
If Not item Is Nothing Then
If item.Type=RICHTEXT Or item.Type=UNKNOWN Then
getFldValueString ="<Item not available for Log>"
Exit Function
End If
Forall vls In item.Values
If Isnumeric(vls) Or Isdate(vls) Then
valStr$= Cstr(vls)
Else
valStr$= vls
End If
vlString = vlString & Chr(13) & valStr$
End Forall
End If
getFldValueString = Trim(vlString)
End Function
Function createTable(rowNumber As Integer,body As NotesRichTextItem) As NotesRichTextNavigator
Dim rtNav As NotesRichTextNavigator
Dim rtTable As NotesRichTextTable
Dim styles() As NotesRichTextParagraphStyle
Redim styles(1 To 6) As NotesRichTextParagraphStyle
For i% = 1 To 6 Step 1
Set styles(i%) = s.CreateRichTextParagraphStyle
styles(i%).LeftMargin = 0
styles(i%).FirstLineLeftMargin = 0
If i% = 6 Or i% = 5 Or i% = 4 Then
styles(i%).Alignment = ALIGN_CENTER
styles(i%).RightMargin = RULER_ONE_INCH * 1
Else
styles(i%).Alignment = ALIGN_CENTER
styles(i%).RightMargin = RULER_ONE_INCH * 1.5
End If
Next
Set rtNav = body.CreateNavigator
Call body.AppendTable(rowNumber+1,6,styles)
Call drawLabel(rtNav,6,body)
Set rtTable = rtNav.GetLastElement(RTELEM_TYPE_TABLE)
totalRowsinTableBefore= 1
totalRowsinTable = rtTable.RowCount
totalColsinTable = rtTable.ColumnCount
Set createTable = rtNav
End Function
Sub printValuesinTable(rtNav As NotesRichTextNavigator,body As NotesRichTextItem)
Dim firstCellinRow As Integer
Dim lastCellInRow As Integer
Dim ArrCellVals() As Variant
For i%=totalRowsinTableBefore+1 To totalRowsinTable Step 1
lastCellInRow = i%*totalColsinTable
firstCellInRow= lastCellInRow - (totalColsinTable-1) 'Total cols in table -1
Redim Preserve ArrCellVals(a%) As Variant
ArrCellVals(a%)= FldNameArr(c%)
Redim Preserve ArrCellVals(a%+1) As Variant
ArrCellVals(a%+1)= FldValuesQueryMode(c%)
Redim Preserve ArrCellVals(a%+2) As Variant
ArrCellVals(a%+2) = FldVlsBackupTerminate(c%)
Redim Preserve ArrCellVals(a%+3) As Variant
ArrCellVals(a%+3) = Now
Redim Preserve ArrCellVals(a%+4) As Variant
ArrCellVals(a%+4) = s.CommonUserName
Call fillRowinTable(rtNav,firstCellInRow,ArrCellVals,lastCellInRow,body)
a%=0
c% = c%+1
Next
End Sub
Sub fillRowinTable(rtNav As NotesRichTextNavigator,firstCellInRow As Integer,ArrCellVals As Variant,lastCellInRow As Integer,body As NotesRichTextItem)
ArrCellValsInt%=0
For i%=firstCellInRow To lastCellInRow Step 1
Call rtNav.FindLastElement(RTELEM_TYPE_TABLE)
Call rtNav.FindNthElement(RTELEM_TYPE_TABLECELL,i%)
Call body.BeginInsert(rtNav)
If i%=lastCellInRow Then
Call body.AppendDocLink(doc,"Click to open the respective document")
Else
Call body.AppendText(ArrCellVals(ArrCellValsInt%))
End If
Call body.EndInsert
ArrCellValsInt%=ArrCellValsInt%+1
Next
End Sub
Function drawLabel(rtNav As NotesRichTextNavigator,cols As Integer,body As NotesRichTextItem) As Boolean
Dim colLabels(0 To 6 ) As String
Dim rtRange As NotesRichTextRange
Dim rtTable As NotesRichTextTable
colLabels(1)="Field Name"
colLabels(2)="Old Value"
colLabels(3)="New Value"
colLabels(4)="Modified On"
colLabels(5)="Modified By"
colLabels(6)="Document Link"
For ind% = 1 To cols Step 1
'Call rtNav.FindLastElement(RTELEM_TYPE_TABLE)
'Call body.AppendText("ooooooo")
Call rtNav.FindNthElement(RTELEM_TYPE_TABLECELL,ind%)
Call body.BeginInsert(rtNav)
Call body.AppendText(colLabels(ind%))
Call body.EndInsert
Next
End Function
Function getFldNameArr(ldoc As NotesDocument) As Variant
Dim ldocArr() As Variant
Forall fldName In ldoc.LogFields
Redim Preserve ldocArr(i%) As Variant
ldocArr(i%) = fldName
i%=i%+1
End Forall
getFldNameArr = ldocArr
End Function
Sub sendSecurityMail(logSettingDoc As NotesDocument, doc As NotesDocument,logActualDoc As NotesDocument)
Dim maildoc As NotesDocument
Dim rtitem As notesRichTextItem
Dim HRname As New NotesName(s.UserName)
Set maildoc = New NotesDocument( db)
maildoc.form = "Memo"
maildoc.SendTo = logSettingDoc.MailPerson
If logSettingDoc.MailToUser(0)="Yes" Then
Dim curuser As New NotesName(s.UserName)
maildoc.CopyTo = curuser.Abbreviated
End If
maildoc.Subject = "Security Log Alert: " & s.CommonUserName & " has edited the document"
Set rtitem = New NotesRichTextItem( maildoc, "Body" )
Call rtitem.AppendText( "The document has been edited by " & HRName.Abbreviated )
Call rtitem.AddNewline(2)
Call rtitem.AppendText( "Respective documents' links are as follows:- ")
Call rtitem.AddNewline(2)
Call rtitem.AppendText( "Source Document Link -> " )
Call rtitem.AppendDocLink( doc, "Click" )
Call rtitem.AddNewline(2)
Call rtitem.AppendText( "Log Document Link -> " )
Call rtitem.AppendDocLink( logActualDoc, "Click" )
Call rtitem.AddNewline(2)
Call rtitem.AppendText( "Log Setting Document Link -> " )
Call rtitem.AppendDocLink( logSettingDoc, "Click" )
Call maildoc.Send(False)
End Sub
Function checkName(logDoc As NotesDocument) As Boolean
Dim dominoDirectory As New NotesDatabase(db.Server,"names.nsf")
Dim grpView As NotesView
Dim grpDoc As NotesDocument
Dim grpMemItem As NotesItem
Set grpView = dominoDirectory.GetView("Groups")
Forall namesStr In logDoc.LimitedPerson
Set grpDoc = grpView.GetDocumentByKey(namesStr)
If Not grpDoc Is Nothing Then
Set grpMemItem = grpDoc.GetFirstItem("Members")
If Not grpMemItem Is Nothing Then
If grpMemItem.Contains(s.UserName) Then
checkName=True
Exit Function
End If
End If
Else
If namesStr=s.CommonUserName Then
checkName=True
Exit Function
End If
End If
End Forall
End Function
Sub updateDocIDInDelete(deleteDoc As NotesDocument)
On Error Goto errHandler
Dim logDoc As NotesDocument
Dim pdoc As NotesDocument
Dim item As NotesItem
Dim itemTemp As Variant
Dim findValue() As String
Dim repValue() As String
Dim searchValue As Variant
Set pdoc= db.GetProfileDocument(deleteDoc.ProfileName(0))
'''''''''''''''''''''''''''''''''''' Getting respective Log Document'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim logDb As New NotesDatabase(db.Server,pdoc.LogDBPath(0))
If Not logDB Is Nothing Then
If logDB.IsOpen=False Then
Call logDB.Open(db.Server,pdoc.LogDBPath(0))
End If
Set logDoc = New NotesDocument(logDB)
Set logDoc=logDB.GetDocumentByUNID(deleteDoc.LogDocID(0))
If Not logDoc Is Nothing Then
Set item = logDoc.GetFirstItem("DBFormFieldAffected")
If Not item Is Nothing Then
Msgbox Ubound(item.Values)
item.Values = Replace(item.Values,deleteDoc.UniversalID,"")
eval = Evaluate("@Trim(DBFormFieldAffected)",logDoc)
item.Values =eval
Msgbox Ubound(item.Values)
Call logDoc.Save(True,False)
End If
Else
Exit Sub
End If
End If
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Exit Sub
errHandler:
Msgbox "An error occured at line : " & Erl & "Error is: " & Error$
Exit Sub
End Sub
Function deleteID(item As NotesItem,id As String) As NotesItem
Forall vls In item.Values
If vls= id Then
'replace(item.Values,vls,id)
End If
End Forall
End Function





4. View: - (“(Security)”)

This view contains two columns to get the form name and unique ID of all the documents within database respectively.
Code Attachments
New Version.zip (119 Kbytes)
Openntf.zip (208 Kbytes)
 Comments
Posted by Kevin Pettitt on 04/01/2007 10:27:22 PMThis looks interesting but why is the script code not in the download db?
Seems unnecessarily confusing to have sample database downloads but then require the developer to copy and paste loose code into a new script library that should have already been part of the design. Hopefully this effort will be worth it...
Posted by Ali Ahmad on 04/02/2007 03:13:40 AMSecurity Log Alert
You can copy the code and paste it in to the "options" section of the script lib and give the name "security" , it will work as is......
Posted by Kevin Pettitt on 04/02/2007 09:24:39 AMI figured that out, just frustrated I had to...
Ali,
I'd really love to get this code working but the lack of a functional example means I have to invest an hour or more figuring out what needs to be done even before I know whether I'll like the way it works. Don't get me wrong, I think its fantastic that you and others contribute to OpenNTF. But the biggest obstacle to usefulness is not how good the code is, but rather how easy it is to find out how good the code is. Ideally someone should be able to download the example and within 5-10 minutes be putting the code to use, seeing how it operates. In this case, a sample form with the code already embedded, some documents pre-populated, and the necessary configuration documents defining which fields to track, would have helped greatly.
I'm working on a big OpenNTF project that I hope to release soon and would like to include some code like this in the standard functionality. I'd really appreciated it if you have time to polish the examples a bit so I can make a proper assessment of whether this is really suitable for my use. I would of course credit your contribution in that case.
Thanks!
Kevin
www.lotusguru.com
Posted by Kevin Pettitt on 04/02/2007 10:14:30 AMFound a similar example that shows what I mean...
Ali,
I found another document edit tracking sample here: http://www.openntf.org/Projects/codebin/codebin.nsf/CodeBySubContributor/71E930683C3401A3C1256F2F00611BAA
Michael has a good sample database on his download page that allowed me to get going quickly. While his code works really well, it requires the creation of a field array inside the actual form events, which means his code is not configurable like yours. I actually already have a "Form Configuration" document in the design of my database, to which I can add a section to specify which fields to track on the form. That piece would resemble your config form, as well as a similar form I built years ago when working on another field auditing project. My next step would be to integrate Michael's form event code into a modular subform that could be included on any form, which would read in values for the field array from the config doc (which looks like what your code should do).
In short, don't spend too much time reworking any of your code for its own sake, but if you think you have any additional features or functionality that you can add over what Michael's History Class already does, we'd love the contribution. You might want to wait a bit until I release my "SuperNTF" project which looks like will use the History Class linked in with my Form Configuration. I will be looking to have people like yourself tear that database apart and help debug/enhance it. Stay tuned.
Posted by Ali Ahmad on 04/02/2007 11:04:17 AMSecurity Log Alert in a full go........
Hi Kevin
Actually the history of this project is that I made it with a lot of effort and prsented on some dev forums of Lotus but unfortunately never got any respond. At my last glance over it I tried openNTF but this was a half hearted attempt. Now I am publising a full version of it with a sample database (named LogTest.nsf) and the setting DB which I already shared with you guys. This time you would not have to configure anything as I have done all the configuration. You just need to donwload and copy the databases in the local data folder named "openNTF" and then see it's functioning (I have checked at my place howerver even then you face any problem feel free to contact).
This database is total configurable and in the setting document of Log Profile database you can even name the person to whome email should be generated in case of any value is changed. Moreover it also includes a list of people, if anyone of them would change the value it will not generated any alert or log.
So try it and do get back to me with your feedback. Even if you feel that it needs to be imporved (which is for sure i don't say it's perfect) do ping me and give your precious ideas. I will definately compare it with the other database that u sent.
Thanks A lot for your interest..........
Posted by Ali Ahmad on 04/02/2007 11:06:40 AMit's a zip file so u would not have to make "openNTF" folder in local directory i.e. notes\data
it's a zip file so u would not have to make "openNTF" folder in local directory i.e. notes\data
Posted by Ali Ahmad on 04/11/2007 12:04:01 AMCode Working
HI Kevin!!!
This is to just confirm that did u find the code working ???
Posted by Kevin Pettitt on 04/15/2007 11:52:15 PMI modified the HistoryClass routines to be configurable
Hi Ali,
I actually haven't had a chance to play with your code yet since I was able to add configurability to the HistoryClass code and encapsulate the calls into a single subform. Send me an email and I'll send you that code so you can have a look at what I've done. Maybe you can get a few ideas to further improve your code. The new download you've posted is still a bit confusing in that there are no clear instructions of how to get started and no sample documents to show what the final result should look like. The lesson here is that time spent writing brilliant code is only well spent if you also spend sufficient time explaining how to use your brilliant code.
Hope that's helpful.
-Kevin
www.lotusguru.com
Posted by Ali Ahmad on 04/16/2007 04:37:17 AMUsing Document of the Database
HI Kevin,
I have added the using document and changed some labels of the databases, I hope it will help this time................................
My email is aliahmad@lmkr.com
Thanx
 Add your comment!