| Solstice documentation | view source | Contained in the Solstice distribution. |
Solstice::Controller::Application::Main - Controls the lifcycle of Solstice requests.
No symbols exported.
sub runApp { my $self = shift; my $screen = shift;
$self->_setScreen($screen);
# session, user, js and so on
# Could enforce url level auth
return unless $self->_denyUnstableBrowser();
return unless $self->_initializeSession();
return if $self->requiresURLAuth();
return unless $self->_isLoginValid();
return unless $self->_checkJavascript();
return unless $self->_initializeState();
return unless $self->loadApplicationForNamespace();
return unless $self->_handleButtonAction();
$self->_setPreClickState(Solstice::Session->new()->getStateTracker()->getState());
return unless $self->_handlePreClick();
return unless $self->_paintPreClickView();
$self->_getPreClickController()->finalize();
return TRUE;
}
sub requiresURLAuth { my $self = shift;
my $config = $self->getConfigService();
my $auth_controller = Solstice::Controller::Application::Auth->new();
if ($self->_getRequiresAuth() ){
if (!$auth_controller->hasAuthenticated()) {
return $auth_controller->requiresUserLogin();
}
elsif (!$auth_controller->hasUserAuth()) {
$self->_paintView(Solstice::View::DeniedUser->new());
return TRUE;
}
}
return FALSE;
}
sub _paintPreClickView { my $self = shift;
my $preclick_controller = $self->_getPreClickController();
my $view = $preclick_controller->getView();
$view->setError($preclick_controller->getController()->getError());
return $self->_paintView($view);
}
sub _paintView { my $self = shift; my $view = shift;
my $application = $self->_getApplication();
my $server = Solstice::Server->new();
my $screen = $self->_getScreen();
$view->sendHeaders();
if($view->isDownloadView()){
$server->printHeaders();
$view->printData();
}else{ ### The traditional view
my $config = $self->getConfigService();
### Init the Application View
my $app_view = Solstice::View::Application->new($application, 1);
$app_view->setEscapeFrames($self->_getEscapeFrames());
$app_view->setDocumentTitle($self->_getDocumentTitle());
$app_view->setSessionAppKey('__'.Solstice::NamespaceService->new()->getNamespace().'__');
#$app_view->addChildView('solstice_popin_container', Solstice::View::PopIn->new());
### Now create the boilerplate View
my $boiler_class;
if ($self->_getBoilerplateView()) {
$boiler_class = $self->_getBoilerplateView();
$self->loadModule($boiler_class);
}elsif ($config->getBoilerplateView()) {
$boiler_class = $config->getBoilerplateView();
$self->loadModule($boiler_class);
} else {
$boiler_class = 'Solstice::View::Boilerplate';
}
my $boiler_view = $boiler_class->new($application);
$boiler_view->setViewTopNav($self->_getViewTopNav()) if $boiler_view->can('setViewTopNav');
$app_view->addChildView('boilerplate', $boiler_view);
$boiler_view->addChildView('content', $view);
$app_view->addChildView('developer_toolbar', Solstice::View::Developer::Toolbar->new($application));
# Paint the App View - the content is returned by the screen
# scalar ref
$app_view->paint($screen);
}
return TRUE;
}
sub _setDevelopmentOptions { my $self = shift;
my $application = $self->_getApplication();
my $preference_service = $self->getPreferenceService($application->getNamespace());
$Solstice::View::use_wireframes = $preference_service->getPreference('display_wire_frames');
$Solstice::LangService::display_tags = $preference_service->getPreference('display_lang_tags');
return TRUE;
}
sub _handlePreClick { my $self = shift;
my $application = $self->_getApplication();
my $state_tracker = Solstice::Session->new()->getStateTracker();
my $selected_button = $self->getButtonService()->getSelectedButton();
#most of the time a preclick will have been set, if not, pull from state tracker the
#default for this url or pageflow
my $preclick_controller = $self->_getPreClickController();
unless( $preclick_controller ){
$preclick_controller = $state_tracker->getController($application);
}
if (defined $selected_button) {
$preclick_controller = $self->_preClickFallbacks($preclick_controller);
}
if ($preclick_controller->getRequiresAuth() &&
$self->_requiresAuthentication()) {
return FALSE;
}
unless ($preclick_controller->getController()){
my $ref = ref $preclick_controller ;
die("$ref did not set a controller in it's constructor. Solstice cannot continue\n");
}
$preclick_controller->initialize();
$self->_setPreClickController($preclick_controller);
return TRUE;
}
sub _preClickFallbacks { my $self = shift; my $preclick_controller = shift;
my $application = $self->_getApplication();
my $state_tracker = Solstice::Session->new()->getStateTracker();
#If we do not have a transition, eg for a bad back button or default state,
#we cannot check for freshen
my $transition = $self->_getTransition();
if ( $transition && $transition->requiresFresh() ) {
if (!$preclick_controller->freshen()) {
return $self->_preClickFallbacks($state_tracker->failFreshen());
}
}
if (!$preclick_controller->validPreConditions()) {
return $self->_preClickFallbacks($state_tracker->failValidPreConditions($application));
}
return $preclick_controller;
}
sub _handleButtonAction { my $self = shift;
my $state_tracker = Solstice::Session->new()->getStateTracker();
# Get the user action and destination application
my $selected_button = $self->getButtonService()->getSelectedButton();
if ($self->getConfigService()->getDevelopmentMode()) {
$self->_setDevelopmentOptions();
}
if(!defined $selected_button){
return TRUE;
}
$self->_setPostClickState($state_tracker->getState()); # for logging only
my $action = $selected_button->getAction();
#if the button clicked was a navigation-only link (a "StaticButton")
#we do not want to act as if any action was selected
if($action eq '__nav_only__'){
return TRUE;
}
$self->_setAction($action);
my $session = Solstice::Session->new();
# __bad_back_button__ is the action on the ajax submit that blocks the back button,
# getIllegalSession() traps the person who manages to click a button on a bad screen
if ($action eq '__bad_back_button__' || $session->getIllegalSession()) {
return $self->_handleBadBackButton();
}
if ($action eq "__set_preference__") {
return $self->_handlePreferenceButton();
}
if ($action eq "__requires_auth__") {
return $self->_handleLoginButton();
}
# Clear the session history if the user should not be able to use
# the back button after this transition.
if ( !$state_tracker->canUseBackButton($action) || $self->_getDisableBackButton() ) {
#Clear the session history so this can't be backed over.
$session->deleteSubsessionChain();
}
$self->_findTransition($action);
return FALSE if ($self->_requiresPostClickAuthentication());
# Returns false if validation fails - stay on the current state.
if ($self->_runPostClickLifeCycle()) {
if ($self->_getIsGlobalTransition()) {
$self->_runGlobalTransition();
}else{
#run standard transition
$state_tracker->transition($action);
}
#reset application incase we changed pageflows
$state_tracker->getState() =~ /^(.*?):/;
my $namespace = $1;
$self->_setNamespace($namespace);
$self->loadApplicationForNamespace();
}
return TRUE;
}
sub _requiresPostClickAuthentication { my $self = shift; my $transition = $self->_getTransition(); my $state_tracker = Solstice::Session->new()->getStateTracker(); my $application = $self->_getApplication(); my $postclick_controller = $state_tracker->getController($application); $self->setPostClickController($postclick_controller);
if ($postclick_controller->getRequiresAuth() &&
$self->_requiresAuthentication()) {
return TRUE;
}
return FALSE;
}
sub _requiresAuthentication { my $self = shift;
my $auth_controller = Solstice::Controller::Application::Auth->new();
if (!$auth_controller->hasUserAuth() ) {
$auth_controller->getAuthentication();
return TRUE;
}
return FALSE;
}
sub _runPostClickLifeCycle { my $self = shift;
my $transition = $self->_getTransition();
my $state_tracker = Solstice::Session->new()->getStateTracker();
my $application = $self->_getApplication();
my $postclick_controller = $self->_getPostClickController();
if ($transition->requiresRevert()) {
if (!$postclick_controller->revert()) {
$self->_setPreClickController($state_tracker->failRevert($application));
return FALSE;
}
}
if ($transition->requiresUpdate()) {
if (!$postclick_controller->update()) {
$self->_setPreClickController($state_tracker->failUpdate($application));
return FALSE;
}
}
if ($transition->requiresValidation()) {
if (!$postclick_controller->validate()) {
if (my $controller = $state_tracker->failValidation($application)) {
$self->_setPreClickController($controller);
} else {
$self->_setPreClickController($postclick_controller);
}
return FALSE;
}
}
if ($transition->requiresCommit()) {
if (!$postclick_controller->commit()) {
$self->_setPreClickController($state_tracker->failCommit($application));
return FALSE;
}
}
return TRUE;
}
sub _runGlobalTransition { my $self = shift;
my $transition = $self->_getTransition();
my $target_pageflow = $transition->getTargetPageFlow();
my $machine = Solstice::State::Machine->new();
my $application = $self->_getApplication();
my $state_tracker = Solstice::Session->new()->getStateTracker();
#assume main page flow if none is specified
my $pageflow = $application->getNamespace().'::'.((defined $target_pageflow) ? $target_pageflow : 'Main');
my $state;
if ("Solstice::State::FlowTransition" eq ref($transition)) {
$pageflow = $transition->getPageFlowName();
my $current_pageflow = $machine->getPageFlow($pageflow);
$state = $current_pageflow->getEntrance();
}else{
$state_tracker->clearPageFlowStack();
}
$state_tracker->startApplication($pageflow, $transition->getName());
$state_tracker->_setState($transition->getTargetState()||$state);
return;
}
sub _findTransition { my $self = shift; my $action = shift;
my $state_tracker = Solstice::Session->new()->getStateTracker();
if( my $transition = $state_tracker->getTransition($action)){
$self->_setTransition($transition);
$self->_setIsGlobalTransition(FALSE);
}elsif( $transition = $state_tracker->getPageFlowGlobalTransition($action, $self->_getNamespace())){
$self->_setTransition($transition);
$self->_setIsGlobalTransition(TRUE)
}elsif( $transition = $state_tracker->getGlobalTransition($action, $self->_getNamespace())){
$self->_setTransition($transition);
$self->_setIsGlobalTransition(TRUE);
}else{
my $state = $state_tracker->getState();
die "Could not transition from state $state via action $action\n"
}
}
sub _handleBadBackButton { my $self = shift;
my $state_tracker = Solstice::Session->new()->getStateTracker();
my $application = $self->_getApplication();
if( $state_tracker->getBackErrorMessage() ){
my $ns = Solstice::NamespaceService->new()->getAppNamespace();
$self->getMessageService()->addInfoMessage(
$self->getLangService($ns)->getMessage($state_tracker->getBackErrorMessage())
);
}else{
$self->getMessageService()->addInfoMessage(
$self->getLangService()->getMessage('generic_back_error')
);
}
$self->_setPreClickController($state_tracker->getController($application));
return TRUE;
}
sub _handlePreferenceButton { my $self = shift;
my $selected_button = $self->getButtonService()->getSelectedButton();
my $application = $self->_getApplication();
my $state_tracker = Solstice::Session->new()->getStateTracker();
my $key = $selected_button->getPreferenceKey();
my $value = $selected_button->getPreferenceValue();
if (defined $key) {
my $preference_service = $self->getPreferenceService($application->getNamespace());
$preference_service->setPreference($key, $value);
}
my $controller = $state_tracker->getController($application);
$controller->update();
$self->_setPreClickController($controller);
# This ensures that dev toolbar prefs get picked up immediately
if ($self->getConfigService()->getDevelopmentMode()) {
$self->_setDevelopmentOptions();
}
return TRUE;
}
sub _handleLoginButton { my $self = shift;
my $selected_button = $self->getButtonService()->getSelectedButton();
my $application = $self->_getApplication();
my $state_tracker = Solstice::Session->new()->getStateTracker();
my $controller = $state_tracker->getController($application);
$controller->setRequiresAuth(TRUE);
$self->_setPreClickController($controller);
return TRUE;
}
sub _denyUnstableBrowser { my $self = shift;
# Do we support this browser?
if( $self->_isBrowserStable() ){
return TRUE;
}else{
$self->_paintView(Solstice::View::DeniedBrowser->new());
return FALSE;
}
}
sub _initializeSession { my $self = shift;
# Initialize session
my $session = Solstice::Session->new;
# Before we do anything else, make sure we have a proper session
# to work with
unless ($session->hasSession()) {
if($self->_getRequireSession()){
$self->_forceCreateSession();
return FALSE;
}else{
$self->_passiveCreateSession();
}
}
# Okay, we have a useful session - since this is a user-pageview
# controller, we'll need a subsession too
$session->loadSubsession();
return TRUE;
}
sub _isLoginValid { my $self = shift;
# Is the login name valid?
if (my $user = $self->getUserService()->getOriginalUser()) {
# Is this really the first place where we need to load the login realm module?
$self->loadModule($user->getLoginRealm());
unless ($user->getLoginRealm()->isValidLogin($user->getLoginName())) {
$self->_paintView(Solstice::View::DeniedUser->new());
return FALSE;
}
}
return TRUE;
}
sub _checkJavascript { my $self = shift;
my $session = Solstice::Session->new();
# Check that javascript flag has been set
if($self->_getRequireSession()){
if (!defined $session->hasJavascript()) {
my $has_js = param('has_js');
if (!defined $has_js) {
$self->_checkHasJavascript();
return FALSE;
}
$session->setHasJavascript($has_js);
Solstice::ButtonService->new()->setHasJavascript($has_js);
}
Solstice::JavaScriptService->new()->setHasJavascript($session->hasJavascript());
}else{
#if we aren't doing a session bounce we just have to assume
#TODO: this exposes that our JS detection method is kinda weak. Can we do better?
Solstice::JavaScriptService->new()->setHasJavascript(TRUE);
}
return TRUE;
}
sub _initializeState { my $self = shift;
my $session = Solstice::Session->new();
my $namespace = Solstice::NamespaceService->new()->getNamespace();
my $pageflow = $self->_getPageFlow() || 'Main';
my $initial_state = $namespace.'::'.$self->_getInitialState();
$pageflow = $namespace.'::'.$pageflow;
### Init the App's State Machine
my $state_tracker = $session->getStateTracker();
#if we are just navigating via a button, we want to keep our subsesion
#but we need to refresh our state to whatever this url thinks is default
my $clear_state = FALSE;
my $selected_button = $self->getButtonService()->getSelectedButton();
if($selected_button && $selected_button->getAction() eq '__nav_only__'){
$clear_state = TRUE;
}
if (!$state_tracker || $clear_state) {
$state_tracker = new Solstice::State::Tracker();
$state_tracker->startApplication($pageflow, $initial_state);
$session->setStateTracker($state_tracker);
}
#if there was a state tracker and we were ina pageflow, we may need
#to capture the name of that pageflow - if we are in the url's default
#pageflow, this will be essentially a no-op
$state_tracker->getState() =~ /(.*?):/;
$namespace = $1;
$self->_setNamespace($namespace);
return TRUE;
}
Adds a header to the current request that will set a session cookie, but does not force a browser bounce - The session cookie will be send on subsequent requests if it's accepted.
Write a cookie to the browser, and do a meta refresh back to the page. Can't be a 300 redirect, it needs to be a full round trip to the client for the cookie to be processed properly.
Round trip to the client, to verify if javascript is enabled or not.
Check browser/OS combination
sub _getAccessorDefinition { return [ { name => 'RequiresFreshen', key => '_require_freshen', type => 'Boolean', private_get => TRUE, }, { name => 'ViewTopNav', key => '_view_top_nav', type => 'Boolean', private_get => TRUE, }, { name => 'BoilerplateView', key => '_boilerplate_view', type => 'String', private_get => TRUE, }, { name => 'RequiresAuth', type => 'Boolean', private_get => TRUE, }, { name => 'RequireSession', key => '_require_session', type => 'Boolean', private_get => TRUE, }, { name => 'InitialState', key => '_initial_state', type => 'String', private_get => TRUE, }, { name => 'PageFlow', key => '_page_flow', type => 'String', private_get => TRUE, }, { name => 'EscapeFrames', key => '_escape_frames', type => 'Boolean', private_get => TRUE, }, { name => 'DisableBackButton', key => '_disable_back_button', type => 'Boolean', private_get => TRUE, }, { name => 'DocumentTitle', key => '_document_title', type => 'String', private_get => TRUE, }, { name => 'PostClickState', key => '_post_click_state', type => 'String', }, { name => 'PreClickState', key => '_pre_click_state', type => 'String', }, { name => 'PostClickController', key => '_post_click_controller', type => 'Solstice::Controller', private_get => TRUE, }, { name => 'PreClickController', key => '_pre_click_controller', type => 'Solstice::Controller', private_get => TRUE, }, { name => 'Action', key => '_action', type => 'String', }, { name => 'Screen', key => '_screen', type => 'SCALAR', private_get => TRUE, }, { name => 'Namespace', key => '_namespace', type => 'String', private_get => TRUE, }, { name => 'Application', key => '_application', type => 'Solstice::Application', private_get => TRUE, }, { name => 'IsGlobalTransition', key => '_is_global_trans', type => 'Boolean', private_get => TRUE, }, { name => 'Transition', key => '_transition', type => 'Solstice::State::Transition', private_get => TRUE, },
]; }
Solstice::Controller::Application, Solstice::Session, Solstice::View::HelpPane (Solstice::View::HelpPane), Solstice::View::InvalidPreConditions, Solstice::View::Navigation (Solstice::View::Navigation), Solstice::CGI,
Catalyst Group, <catalyst@u.washington.edu>
$Revision: 3375 $
Copyright 1998-2007 Office of Learning Technologies, University of Washington
Licensed under the Educational Community License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
| Solstice documentation | view source | Contained in the Solstice distribution. |