Porting Guidelines

PCX Portal 0.1.06 -> 0.2.00

Config File changes:
XML is now used for all config files and the name of the app is what the config file is called instead of appname/Config.pm.  Ex.  The Portal config file is now Portal.xml.

There is a support script /usr/lib/pcx_portal/convertConfig.pl which will convert a normal Config.pm module to the corresponding xml file, minus any color configuration entries.  All colors are now stored in a colorScheme definition.

To convert your apps Config.pm file you would do:
/usr/lib/pcx_portal/convertConfig.pl TestApp /etc/pcx_portal/TestApp/Config.pm /etc/pcx_portal/TestApp.xml
and then use your converted config file in your project, replacing install/config/Config.pm with install/config/TestApp.xml, updating the MANIFEST accordingly.
You will also need to modify your TestApp/Data/Config.pm module.  Base your changes off the Template App 0.0.08 version.

Next you need to create a colorScheme xml config file to represent any css color schemes you are interested in using since all color config entries are stripped out of your config file when it is converted.  See the docs/Portal_Color_Scheme_Entries.html document for more details on the layout/design of the colorScheme subsystem.


Programming Changes:


Wrap all instantiations of the Portal::Data::Config or your app's Config module in an eval statement since they will die when they encounter an error instead of setting an error flag, etc.

Add the methods object to the new method of your TestApp.pm module and any state modules under TestApp, for example Main.pm, PortalSetup.pm, etc.  You will also need to add the validation code to the isValid() method.  In your TestApp.pm module make sure you add methods => $self->{methods} to the new method of your state modules you instantiate otherwise they will not have access to the Methods module that the Portal is providing.

Update your Language.pm module from the Template App and replace App with TestApp (as appropriate in Portal::App::Language).  This provides the map function for accessing the phrases hash and providing error checking to make sure that a requested phrase does exist.

Now remove any instances of my $phrases = $self->langObj->phraseObj->phrases;
and replace any instances of $phrases->{X} or $self->langObj->phraseObj->phrases->{X} with $self->langObj->map("X").  If you are in a here document ($variable << "END_OF_CODE";) then you will need to assign the phrase to a variable and use it in the here document otherwise you will get the address of the map function output and not the string you are expecting.

Any Portal::Session setup code should now be replaced with:
my $session = $self->{methods}->portalSession(portalDB => $self->{portalDB}, sessionId => "", configObj => $self->{configObj});  # see Portal::Methods for complete details.
if ($self->{methods}->didErrorOccur)
{
  $self->setError(errorString => $errStr . $self->{methods}->errorMessage);
  return $doc;
}

Any Portal::SessionHandler code should be replaced with:
my $appSessions = $self->{methods}->portalSessionHandler(portalDB => $self->{portalDB}, sessionObj => $self->{portalSession}, configObj => $self->{configObj});  # where $self->{configObj} is a Portal::Data::Config object
if ($self->{methods}->didErrorOccur)
{
  $self->setError(errorString => $errStr . $self->{methods}->errorMessage);
  return $self;
}
This assumes we are in the new method of your TestApp.pm module otherwise you would want to return a different object, potentially $doc or undef when an error occurs.

Study the new commands available from Portal::Application, Portal::Auth, Portal::Methods.

I would recommend porting your existing code into the current Template App and then re-naming it back to your app rather than trying to add all the changes into your app and then have to also do any cleanup/optimization that the new methods now provide.

0.2.00 -> 0.2.01

The portal is now Object Oriented in that all modules, other than Language related, are ultimately derived from Portal::Base.  Based on the file you are working with, it may be derived from a higher class that provides functionality that all files of that type have in common.  All objects/modules, etc. now require the langObj variable to be passed in so that errors can be internationalized.

See The Template App version 0.0.09 for an example of how your application should now look.  By moving to a true Object Oriented approach I am able to eliminate a lot of the redundant code and the common error handling is all now taken care of in the base class you derive from.

There is a new Error handling construct now available.  You can continue to use $self->setError(errorString => "") or you can now just call $self->setError("error message goes here").  There are also error hashes that allow you to specify missing, invalid, valid, or unkown parameters to your code.  See Portal::Base for an overview and the available methods to work with the new error structures.

The error display method genErrorString() will HTTP encode any strings being output to the browser that are built up out of the invalid, valid, missing and unknown error hashes.  This is to help eliminate XSS (cross site scripting) capabilities.  I strongly recommend you start moving any error messages possible to using the new error constructs or at least $doc->formEncode() any variables you are going to display in error messages.

portalDB is now passed into the apps being instantiated/run from index.cgi.
configObj will always be Portal::Data::Config.
appConfigObj is defined in the base of the app in use and will point to that app's Portal::App::Data::Config module.  It should be passed into all States of the app.

If you are needing to instantiate a State module that takes all the same parameters as your current module, take advantage of the arguments() method defined by your parent module.  See Portal::App for an example.  You can always add any missing parameters but the arguments() method will make sure that all the parameters that the class needs are specified and this will pick up any new parameters I add in the future.

You need to convert all $errStr phrases as follows:  s/\s{1,2}- Error!<br>\n$/$self->{errorPhrase}/e

You should uriEncode all your urls, especially anything that the user specified or is in a variable.  Use the urlBuilder() method in Portal::Methods to help with this task.

I converted all the Portal and Portal Apps code to start using inline POD but not all the code has been fully converted to inline POD and does not yet fully take advantage of the new error constructs.

0.2.01 -> 0.2.02

Rename Portal::AppArgs -> Portal::App and Portal::AppStateArgs -> Portal::AppState.

Consequently, the Template App program is now Portal::Example instead of Portal::App.  If you had the old version installed you will need to make sure you get rid of all traces of Portal::App:: files before or after you upgrade the Portal.  You will need to delete the entries from the database for Portal::App and re-install the Template App before it will show up again and work.

0.2.02 -> 0.3.00

Note: At a minimum you should read the man pages for Portal::App, Portal::AppState and Portal::Methods.

Remove $errStr since the setError/prefixError/postfixError methods will now generate that for you. :)

Any calls to setError that just did a prefix with your $errStr and then re-iterated the current errorMessage, should be converted to $self->prefixError(); since that will do that for you.

The isValid() method needs better validation code.  A sample block follows:

 sub isValid
 {
   my $self = shift;

   # make sure our Parent class is valid.
   if (!$self->SUPER::isValid())
   {
     $self->prefixError();
     return 0;
   }

   # validate our parameters.

   if ($self->numInvalid() > 0 || $self->numMissing() > 0)
   {
     $self->postfixError($self->genErrorString("all"));
     return 0;
   }
   return 1;
 }

Code in red reflects the major validation changes to make other than removing $errStr.

Closing an app is now simpler to code for, see the Example apps code. Basically you remove the else block contents (saying it is ok to close the app) and call $self->{methods}->processCloseAppEvent(); The appName and appValue parameters are the name of the application (ex. Accounting, Example, UserProperties).

The urlBuilder method now requires the baseUrl attribute so that I can return a complete url of the form baseUrl?arguments.

If doing any Logging to the log_tb, please use the doLog method provided by the Portal::Methods object. It combines the setup and writing code and handles the different scenarios it may be called from (Auth/Application or user code).

The portal now grabs the current time in seconds past the epoc when first creating the Portal Session. This is stored at portalSession->{store}->{windowTitleTimestamp} and is used to make sure that the initial Portal App windows and any help windows for this instance of the Portal are hopefully unique from any other Portals the user may access from the same browser.
The naming scheme is now AppName_windowTitleTimestamp value for the Portal App main windows. For help windows it is help_AppName_windowTitleTimestamp.

The missing and invalid methods can now take an extra piece of data that will be displayed after the error message so you can explain to the user why the entry is needed or is invalid. Ex: $self->invalid("name", "1xy", "The name can not start with a digit!");
Would output "name = '1xy' is invalid! The name can not start with a digit!<br>\n".

Portal::App enhancements
In the new() method, replace all the session setup, langObj creation, database setup code with the following:
  # do anything else you might need to do.

  $self->instantiatePortalApp(dbHost => "Portal");
  if ($self->didErrorOccur)
  {
    $self->prefixError();
    return $self;
  }

As long as you are not doing any extra validation work in the run() method, you can just delete it! You will need to specify the appName, appVersion, logOk, useAppStateCommandSecurity and states variables in your derived class so that the run() code in Portal::App can know about your app and properly handle things.
An Example for Portal::Example is:
  $self->{appName} = "Example";
  $self->{appVersion} = $VERSION;
  $self->{states} = \%states;
  $self->{logOk} = 1;  # we can log safely.  Change to 0 if you don't have a log_tb defined in your database.
  $self->{useAppStateCommandSecurity} = 0;
Now in any States of your application, you can access $self->{appName} and $self->{appVersion} to get the name and version of your app (In this case name = 'Example' and version would be the value of $Portal::Example::VERSION). Suggestion: Start using the $self->{appName} and $self->{appVersion} variables to make modularizing your code and future proofing it.

If you do need to do extra validation or setup code before the state/command that index.cgi called you with is sourced and executed, you can use the preStateCallback variable to specify the function to call (defined in your derived class) so that you can process the state/command coming in and potentially specify a new state/command to work with. The stateEvalErrorCallback variable is used to specify the function to call (defined in your derived class) that will cleanup any changes that may have been made by the preStateCallback function being called. You should define them together (unless you didn't do anything major in the preStateCallback method, such as lock the application).
Ex: In new() you would add:
  $self->{preStateCallback} = "verifyDatabaseVersion";
  $self->{stateEvalErrorCallback} = "unlockDatabase";
You would then create the following functions (not fleshed out):
sub verifyDatabaseVersion
{
  my $self = shift;
  my %args = ( state => "", command => "", @_ );
  my $state = $args{state};
  my $command = $args{command};

  # lookup the current database version

  if ($dbversion != $CurrentVersion)
  {
    # lock the application

    # signal we need to upgrade the database.
    return ("Portal::Accounting::DB::Upgrade", "display");
  }
  # return the state/command sent in so that we still go where we were trying to.
  return ($state, $command);
}

sub unlockDatabase
{
  my $self = shift;
  my %args = ( state => "", command => "", @_ );
  my $state = $args{state};
  my $command = $args{command};

  # see if the database is locked, if so, unlock it.
  if ($state eq "Portal::Accounting::DB::Upgrade")
  {
  }
}
If you need extra arguments to be passed into the states of your app, xiwa does, then set the
  $self->{extraArguments} = "";
parameter in new(). The contents are a comma delimited => hash elements that are assigning static values.

Language Objects for Portal Apps now will get the contents of the Portal's Language Object, if using the instantiatePortalApp() helper method, though if you had a phrase that the Portal provided also, you do not get the Portal version, just your version.

You also need to add the following code block to your derived Portal::App modules so that the app installer can eventually attempt to setup the necessary rules for using the AppStateCommandSecurity implementation:
=item % getStates(void)

  This method returns the states hash and is only to be
  used by the registerApp method for creating the permissions,
  if using the AppStateCommandSecurity implementation.

=cut
sub getStates
{
  return %states;
}



Portal::AppState enhancements.
In the new() method add:
  $self->{commands} = \%commands;
If you were instantiating the $self->{portalVariables} variable to get Portal::Data::Variables, this is now being done by Portal::AppState::new(), so you can delete that setup code in your derived modules.

If you want to take advantage of the run() method that Portal::AppState now implements, read the Portal::AppState manpage and follow the example. Remove your derived modules run() method otherwise you won't be using the Portal::AppState implementation.

You also need to add the following code block to your derived Portal::App modules so that the app installer can eventually attempt to setup the necessary rules for using the AppStateCommandSecurity implementation:
=item % getCommands(void)

  This method returns the commands hash and is only to be
  used by the registerApp method for creating the permissions,
  if using the AppStateCommandSecurity implementation.

=cut
sub getCommands
{
  return %commands;
}

Look at using the setupCSS() method to help simplify getting an HTMLObject::Normal document, the users colorScheme and any core Javascript files the Portal requires.