The idea occurred to me when I inherited a system written originally in VFP 6, containing a very cumbersome system of allocating permits and functions to the different users of the network. It consists of a series of case statements that would define pads, popups or menu bars, according to the status of certain boolean properties. If the properties were set to true, the corresponding lines of code would be “allowed” or “disallowed” if they were set to false. The more users the Company had, the more involved the process of authorizing menu items. As in all organizations, some people are allowed to see the full menu (generally managers), while others can only see a limited portion of it, usually the one that relates to their job duties.
So, faced with the prospect of maintaining that system and consistent with my generally lazy nature, I decided to change it and use a more manageable approach.
Outline of the process
A table containing the user names and passwords, plus a memo field to save the menus for each user in the form of an XML file, is basically all that is needed.
When a user logs in, and after verifying his identity and rights, the XML data contained in the memo field is retrieved and converted into a cursor. A scan through this cursor builds the menu, which is then attached to a top-level form.
Simple, huh? Yes, in theory, but, how do you get the XML data in the users table in the first place?
How it is done
As in many things Fox, there are different approaches to do this. But to me, the easiest way is to use the VFP menu builder and create the full menu with all possible pads, popups, subpopups and bars, as if it were to be used in the usual way.
However, instead of marking in the General Options the checkbox for ‘top-level form’, we leave it unmarked. Another thing we would have to care for is to name every pad by clicking on the Options button and writing a suitable name in the pad name textbox. We then generate the MPR. In this manner, the VFP menu builder will create a file with very few comments and using very few random names, which, when you analyze this mpr file, are obscure and difficult to understand.
What I mean is, if your pads are something like this:
by naming the pads in the Options sections, we force the builder to use our names, and not the random group of letters and numbers which the menubuilder obtains by using the sys(2015) function. So, in the above example, we should use names such as Invoices, custaccs,stocks,prices,tables, etc.
The menu generator uses the default name of _MSYSMENU to name the main menu bar, because the menu will supposedly replace the VFP standard menu which is shown in the VFP screen. But we are going to use a Top Level Form to display our menu and it would be advisable to use a more meaningful name such as MAINMENU or whatever you like. So we change all references to _MSYSMENU to MainMenu.
We get rid of all comment lines and two additional lines, the builder places at the very top:
SET SYSMENU TO SET SYSMENU AUTOMATIC
Lastly, our code will probably contain things like this:
ON SELECTION BAR 2 OF ACCOUNTING ; DO _1vu0zdd89 ; IN LOCFILE("MYPROJECTS\MAINMENU" ,"MPX;MPR|FXP;PRG" ,"WHERE is MAINMENU?")
ON SELECTION BAR 2 OF ACCOUNTING DO menuprocs with ‘accounting’
Do case Case .... Case .... Case cOption = ‘accounting’ Public oAccounting Do form accounting name oAccounting linked Etc
Don’t worry about spaces between lines, later on in this article you will see how we ignore empty lines.
In the end, we will have a ‘clean’ MPR file which we would turn into a cursor which will subsequently be converted to XML and finally saved in the users table.
Convert the MPR file into a cursor
Use in Select("auxilmenu") create cursor auxilmenu ( mprmenu M ) Select auxilmenu append blank append memo mprmenu from mainmenu.mpr
Local i, cLine, nValue nValue = 0 Use in Select("mastermenu") Create Cursor mastermenu( menuid I,; theprompt C(100),; allowed L; ) nValue = 0 Set Memowidth to 2000 Select auxilmenu For i = 1 to Memlines(mprmenu) cLine = Alltrim(Mline(mprmenu,i)) If Len(cLine) = 0 && this is where we get rid of empty lines Loop Else nValue = nValue + 1 Insert into mastermenu(menuid,theprompt,allowed) ; values(nValue,cLine,.t.) endif EndFor
oXML = Createobject("XMLAdapter") oXML.AddTableSchema("mastermenu") oXML.ToXML("cXML")
Select theusers Locate for Upper(Alltrim(username)) == 'MASTERMENU' If Found() Select theusers replace themenu with cXML Else Insert into theusers(username,themenu) values ‘MASTERMENU’,cXML) EndIf
What do we do now with this cursor?
It is now possible to use this cursor and assign different functions to all users. The process consists of calling the mastermenu XML file from its “safe deposit” in the memo field, converting it back to a cursor, mark the “allowed” field with .f. for every function disallowed, convert it back to XML and save it in the memo field, this time under the user’s name.
Let us do it.
Assign the modified mastermenu to the user
Local cXML Use in Select("mastermenu") Select theusers Locate for Upper(Alltrim(username))=='MASTERMENU' If found() cXML = theusers.themenu XMLToCursor(cXML,'mastermenu',4) Update mastermenu set allowed = .t. && in the master menu everything is allowed Go top in mastermenu endif
In this manner, some functions will be allowed and others will not (this is the special case of the user George). We now convert this specific menu to XML, and save it in the George record memo field.
oXML = Createobject("XMLAdapter") oXML.AddTableSchema("mastermenu") oXML.ToXML("cXML") Select theusers Locate for upper(alltrim(username)) == ‘GEORGE’ If found() replace themenu with cXML EndIf
How is the user’s menu generated?
The following day we have to tackle with the problem of generating this specialized menu every time George logs in.
So, after George logs in (use a login form for that), we pull the XML from his record in the "theusers" table, convert it into a cursor and iterate through all the records, building the menu.
We are going to use this menu in a top level form, and for the purpose of this example the TLF will be called "Main", called from the application starting program with:
Do form main name oMain linked Read events
Select theusers Locate for upper(alltrim(username)) == "GEORGE" If found() cXML = theusers.themenu XMLToCursor(cXML,'mastermenu',4) Endif
Release Menus mainmenu extended define Menu mainmenu bar in window (thisform.Name) Select mastermenu Scan for allowed = .t. cOption = theprompt &cOption EndScan Activate Menu mainmenu nowait
Conclusion
I tried to simplify the process of assigning functions to users, only showing those parts of the menu that concern them, hiding from them the confidential or not necessary parts.
There is a lot more to be done with this approach, such as providing for modifications to the main menu and passing them to all users in the users table. Another permutation would be to give some users full modification rights and others just read only rights for the same routine. I think that all of this can be accomplished by adding specific fields to the menu cursor. After all, when a cursor is shown in a grid, marking or unmarking records is very easy.