{
    $Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
    This file is part of the Free Component Library (FCL)
    Copyright (c) 1999-2000 by the Free Pascal development team

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{$mode objfpc}
{$H+}
unit fpWeb;

interface

uses
  Classes, SysUtils, httpdefs, fphttp, fptemplate;

Type

  { TFPWebAction }

  TFPWebAction = Class(TCustomWebAction)
  Private
    FOnrequest: TWebActionEvent;
    FContents : TStrings;
    FTemplate : TFPTemplate;
    function  GetContents: TStrings;
    procedure SetContents(const AValue: TStrings);
    Procedure SetTemplate(const AValue : TFPTemplate);
  Protected  
    Procedure DoHandleRequest(ARequest : TRequest; AResponse : TResponse; Var Handled : Boolean); override;
    Procedure DoGetContent(ARequest : TRequest; Content : TStream; Var Handled : Boolean); virtual;
    Procedure GetContent(ARequest : TRequest; Content : TStream; Var Handled : Boolean);
  Public
    Constructor create(ACollection : TCollection); override;
    Destructor destroy; override;
    Procedure Assign(Source : TPersistent); override;
  published
    Property Contents : TStrings Read GetContents Write SetContents;
    Property OnRequest: TWebActionEvent Read FOnrequest Write FOnrequest;
    Property Template : TFPTemplate Read FTemplate Write SetTemplate;
  end;  

  { TFPWebActions }

  TFPWebActions = Class(TCustomWebActions)
  private
    FCurrentAction : TCustomWebAction;
  protected
    Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse; Var Handled : Boolean); virtual;
    Procedure GetContent(ARequest : TRequest; Content : TStream; Var Handled : Boolean); virtual;
  Public
    Property ActionVar;
    Property CurrentAction: TCustomWebAction read FCurrentAction;
  end;

  { TTemplateVar }


  TTemplateVar = Class(TCollectionItem)
  Private
    FName: String;
    FValue: String;
  Public
    Procedure Assign(Source : TPersistent); override;
    Function GetDisplayName : String; override;
  Published
    Property Name : String Read FName Write FName;
    Property Value : String Read FValue Write FValue;
  end;

  { TTemplateVars }

  TTemplateVars = Class(TCollection)
  Private
    function GetVar(I : Integer): TTemplateVar;
    procedure Setvar(I : Integer; const AValue: TTemplateVar);
  Public
    Function IndexOfVar(AName : String) : Integer;
    Function VarByName(AName : String) : TTemplateVar;
    Function FindVar(AName : String) : TTemplateVar;
    Property Variables[I : Integer] : TTemplateVar Read GetVar Write Setvar; default;
  end;

  TContentEvent = Procedure (Sender : TObject; Content : TStream) of object;

  { TCustomFPWebModule }

  TCustomFPWebModule = Class(TSessionHTTPModule)
  private
    FActions: TFPWebActions;
    FAfterResponse: TResponseEvent;
    FBeforeRequest: TRequestEvent;
    FOnGetParam: TGetParamEvent;
    FOnRequest: TWebActionEvent;
    FRequest: TRequest;
    FResponse: TResponse;
    FTemplate: TFPTemplate;
    FTemplateVars : TTemplateVars;
    function GetActionVar: String;
    function GetDefActionWhenUnknown: Boolean;
    function GetOnGetAction: TGetActionEvent;
    procedure SetActions(const AValue: TFPWebActions);
    procedure SetActionVar(const AValue: String);
    procedure SetDefActionWhenUnknown(const AValue: Boolean);
    procedure SetOnGetAction(const AValue: TGetActionEvent);
    procedure SetTemplate(const AValue: TFPTemplate);
  Protected
    Function HandleActions(ARequest : TRequest): Boolean; virtual;
    procedure DoOnRequest(ARequest: TRequest; AResponse: TResponse; var AHandled: Boolean); virtual;
    Procedure DoBeforeRequest(ARequest : TRequest); virtual;
    Procedure DoAfterResponse(AResponse : TResponse); virtual;
    Procedure GetParam(Const ParamName : String; Out Value : String); virtual; // Called by template
    Procedure GetTemplateContent(ARequest : TRequest; AResponse : TResponse); virtual;
    function GetContent: String;virtual;
  Public
    Constructor CreateNew(AOwner : TComponent; CreateMode : Integer); override;
    Destructor Destroy; override;
    Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse); override;
    Property Actions : TFPWebActions Read FActions Write SetActions;
    Property ActionVar : String Read GetActionVar Write SetActionVar;
    Property BeforeRequest : TRequestEvent Read FBeforeRequest Write FBeforeRequest;
    Property OnRequest : TWebActionEvent Read FOnRequest Write FOnRequest;
    Property AfterResponse : TResponseEvent Read FAfterResponse Write FAfterResponse;
    Property OnGetAction : TGetActionEvent Read GetOnGetAction Write SetOnGetAction;
    Property DefActionWhenUnknown : Boolean read GetDefActionWhenUnknown write SetDefActionWhenUnknown default true;
    Property ModuleTemplate : TFPTemplate Read FTemplate Write SetTemplate;
    Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;
    Property OnTemplateContent : TGetParamEvent Read FOnGetParam Write FOnGetParam;
    Property Request: TRequest Read FRequest;
    Property Response: TResponse Read FResponse;
  end;
  
  { TFPWebModule }

  TFPWebModule = Class(TCustomFPWebModule)
  Published
    Property Actions;
    Property ActionVar;
    Property BeforeRequest;
    Property OnRequest;
    Property AfterResponse;
    Property OnGetAction;
    Property DefActionWhenUnknown;
    Property CreateSession;
    Property Session;
    property Kind;
    Property OnNewSession;
    Property OnSessionExpired;
    Property AfterInitModule;
  end;

  EFPWebError = Class(EHTTP);

resourcestring
  SErrInvalidVar        = 'Invalid template variable name : "%s"';
  SErrInvalidWebAction  = 'Invalid action for "%s".';
  SErrNoContentProduced = 'No template content was produced.';

implementation

{$ifdef cgidebug}
uses dbugintf;
{$endif cgidebug}


{ TFPWebAction }

procedure TFPWebAction.GetContent(ARequest: TRequest; Content: TStream; Var Handled : Boolean);

begin
  DoGetContent(ARequest, Content, Handled);
end;

procedure TFPWebAction.Assign(Source: TPersistent);

Var
  A : TFPWebAction;

begin
  If (Source is TFPWebAction) then
    begin
    A:=TFPWebAction(Source);
    Name:=A.Name;
    AfterResponse:=A.AfterResponse;
    BeforeRequest:=A.BeforeRequest;
    Default:=A.default;
    Contents:=A.FContents;
    ContentProducer:=A.ContentProducer;
    OnRequest:=A.OnRequest;
    FTemplate.Assign(A.Template);
    end
  else
    inherited Assign(Source);
end;

constructor TFPWebAction.create(ACollection: TCollection);
begin
  inherited create(ACollection);
  FTemplate:=TFPtemplate.Create;
end;

destructor TFPWebAction.destroy;
begin
  FreeandNil(FContents);
  FreeAndNil(FTemplate);
  inherited destroy;
end;

function TFPWebAction.GetContents: TStrings;
begin
  If Not Assigned(FContents) then
    FContents:=TStringList.Create;
  Result:=FContents;
end;

procedure TFPWebAction.SetContents(const AValue: TStrings);
begin
  if AValue = nil then
    FreeAndNil(FContents)
  else
    Contents.Assign(AValue);
end;

procedure TFPWebAction.SetTemplate(const AValue: TFPTemplate);
begin
  FTemplate.Assign(AValue);
end;


procedure TFPWebAction.DoHandleRequest(ARequest: TRequest; AResponse: TResponse; Var Handled : Boolean);

begin
{$ifdef cgidebug}
  SendMethodEnter('TFPWebAction('+Name+').Dohandlerequest');
  If Handled then
    SendDebug('Handled !!')
  else
    SendDebug('Not yet handled.');
{$endif cgidebug}
  If Assigned(FOnRequest) then
    begin
{$ifdef cgidebug}
    SendDebug('Executing user action');
{$endif cgidebug}
    FOnrequest(Self,Arequest,AResponse,Handled);
    end;
  If Not Handled then
    begin
{$ifdef cgidebug}
    SendDebug('Executing inherited');
{$endif cgidebug}
    Inherited DoHandleRequest(ARequest,AResponse,Handled);
    If not Handled then
      begin
      Handled := (FContents <> nil) and (FContents.Count > 0);
      if Handled then
        AResponse.Contents.AddStrings(FContents);
      end;
    end;
{$ifdef cgidebug}
  SendMethodExit('TFPWebAction('+Name+').Dohandlerequest');
{$endif cgidebug}
end;

procedure TFPWebAction.DoGetContent(ARequest: TRequest; Content: TStream; Var Handled : Boolean);

  //isolate string references in a subprocedure to avoid implicit exceptions in main procedure
  procedure CopyContent;
  var
    ContentStr: String;
  begin
    if FContents <> nil then
    begin
      ContentStr := FContents.Text;
      If ContentStr<>'' then
        Content.Write(ContentStr[1],Length(ContentStr));
    end;
  end;

begin
  If Assigned(ContentProducer) then
    ContentProducer.GetContent(ARequest,Content,Handled)
  else
    CopyContent;
end;


{ TFPWebTemplate }
Type
  TFPWebTemplate = Class(TFPTemplate)
  Private
    FOwner: TCustomFPWebModule;
    FRequest : TRequest;
  Public
    Constructor Create(AOwner :TCustomFPWebModule);
    Procedure GetParam(Sender : TObject; Const ParamName : String; Out AValue : String);override;
    Property Owner : TCustomFPWebModule Read FOwner;
    Property Request : TRequest Read FRequest Write FRequest;
  end;

constructor TFPWebTemplate.Create(AOwner: TCustomFPWebModule);
begin
  Inherited create;
  FOwner:=AOwner;
end;

procedure TFPWebTemplate.GetParam(Sender: TObject; const ParamName: String;
  out AValue: String);
begin
  FOwner.GetParam(ParamName, AValue);
end;

{ TFPWebModule }

function TCustomFPWebModule.GetActionVar: String;
begin
  Result:=FActions.ActionVar;
end;

function TCustomFPWebModule.GetDefActionWhenUnknown: Boolean;
begin
  Result:=FActions.DefActionWhenUnknown;
end;

function TCustomFPWebModule.GetOnGetAction: TGetActionEvent;
begin
  Result:=FActions.OnGetAction;
end;


procedure TCustomFPWebModule.SetActions(const AValue: TFPWebActions);
begin
  if (FActions<>AValue) then
    FActions.Assign(AValue);
end;

procedure TCustomFPWebModule.SetActionVar(const AValue: String);
begin
  FActions.ActionVar:=AValue;
end;

procedure TCustomFPWebModule.SetDefActionWhenUnknown(const AValue: Boolean);
begin
  FActions.DefActionWhenUnknown:=AValue;
end;

procedure TCustomFPWebModule.SetOnGetAction(const AValue: TGetActionEvent);
begin
  FActions.OnGetAction:=AValue;
end;


procedure TCustomFPWebModule.SetTemplate(const AValue: TFPTemplate);
begin
  if FTemplate<>AValue then
    FTemplate.Assign(AValue);
end;

function TCustomFPWebModule.HandleActions(ARequest: TRequest): Boolean;
begin
  Result:=True;
end;

procedure TCustomFPWebModule.DoBeforeRequest(ARequest : TRequest);
begin
  If Assigned(FBeforeRequest) then
    FBeforeRequest(Self,ARequest);
end;

procedure TCustomFPWebModule.DoAfterResponse(AResponse : TResponse);
begin
  If Assigned(FAfterResponse) then
    FAfterResponse(Self,AResponse);
end;

procedure TCustomFPWebModule.GetParam(const ParamName: String; out Value: String);
  
Var
  T : TTemplateVar;
  
begin
  If (0=CompareText(ParamName,'CONTENT')) then
    Value:=GetContent
  else
    begin
    T:=FTemplateVars.FindVar(ParamName);
    If (T<>Nil) then
      Value:=T.Value
    else
      If Assigned(FOnGetParam) then
        FOngetParam(Self,ParamName,Value);
    end;
end;

procedure TCustomFPWebModule.GetTemplateContent(ARequest: TRequest;
  AResponse: TResponse);
  
begin
  TFPWebTemplate(FTemplate).Request:=ARequest;
  AResponse.Content:=FTemplate.GetContent;
end;

function TCustomFPWebModule.GetContent: String;

Var
  S : TStringStream;
  B : Boolean;
  
begin
  S:=TStringStream.Create('');
  Try
    B:=False;
    FActions.GetContent(TFPWebTemplate(FTemplate).Request,S,B);
    If Not B then
      Raise EFPWebError.Create(SErrNoContentProduced);
    Result:=S.DataString;
  finally
    S.Free;
  end;
end;

constructor TCustomFPWebModule.CreateNew(AOwner: TComponent; CreateMode : Integer);
begin
  inherited;
  FActions:=TFPWebActions.Create(TFPWebAction);
  FTemplate:=TFPWebTemplate.Create(Self);
  FTemplateVars:=TTemplateVars.Create(TTemplateVar);
end;

destructor TCustomFPWebModule.Destroy;
begin
  FreeAndNil(FTemplateVars);
  FreeAndNil(FTemplate);
  FreeAndNil(FActions);
  inherited Destroy;
end;


procedure TCustomFPWebModule.DoOnRequest(ARequest: TRequest; AResponse: TResponse; Var AHandled : Boolean);

begin
  If Assigned(FOnRequest) then
    FOnRequest(Self,ARequest,AResponse,AHandled);
end;

procedure TCustomFPWebModule.HandleRequest(ARequest: TRequest; AResponse: TResponse);

Var
  B : Boolean;

begin
{$ifdef cgidebug}
  SendMethodEnter('WebModule('+Name+').handlerequest');
{$endif cgidebug}
  FRequest := ARequest; //So everything in the web module can access the current request variables
  FResponse := AResponse;//So everything in the web module can access the current response variables
  CheckSession(ARequest);
  DoBeforeRequest(ARequest);
  B:=False;
  InitSession(AResponse);
  DoOnRequest(ARequest,AResponse,B);
  If B then
    begin
    if not AResponse.ContentSent then
      AResponse.SendContent;
    end
  else
    if FTemplate.HasContent then
      GetTemplateContent(ARequest,AResponse)
    else if HandleActions(ARequest) then
      begin
      Actions.HandleRequest(ARequest,AResponse,B);
      FTemplate.Template := '';//if apache mod, then need to clear for next call because it is a webmodule global property,
      FTemplate.FileName := '';//so following calls are OK and the above FTemplate.HasContent is not becoming True
      If Not B then
        Raise EFPWebError.Create(SErrRequestNotHandled);
      end;
  DoAfterResponse(AResponse);
  UpdateSession(AResponse);
  FRequest := Nil;
  FResponse := Nil;
  // Clean up session for the case the webmodule is used again
  DoneSession;
{$ifdef cgidebug}
  SendMethodExit('WebModule('+Name+').handlerequest');
{$endif cgidebug}
end;

{ TTemplateVar }

procedure TTemplateVar.Assign(Source: TPersistent);
begin
  if Source is TTemplateVar then
    With Source as TTemplateVar do
      begin
      Self.Name:=Name;
      Self.Value:=Value;
      end
  else
    inherited Assign(Source);
end;

function TTemplateVar.GetDisplayName: String;
begin
  Result:=FName;
end;

{ TTemplateVars }

function TTemplateVars.GetVar(I : Integer): TTemplateVar;
begin
  Result:=TTemplateVar(Items[I])
end;

procedure TTemplateVars.Setvar(I : Integer; const AValue: TTemplateVar);
begin
  Items[i]:=AValue;
end;

function TTemplateVars.IndexOfVar(AName: String): Integer;

begin
  Result:=Count-1;
  While (Result>=0) and (CompareText(AName,GetVar(Result).Name)<>0) do
    Dec(Result);
end;

function TTemplateVars.VarByName(AName: String): TTemplateVar;
begin
  Result:=FindVar(AName);
  If (Result=Nil) then
    Raise EFPWebError.CreateFmt(SErrInvalidVar,[AName]);
end;

function TTemplateVars.FindVar(AName: String): TTemplateVar;

Var
  I : Integer;

begin
  I:=IndexOfVar(AName);
  If (I=-1) then
    Result:=Nil
  else
    Result:=GetVar(I);
end;

{ TFPWebActions }

procedure TFPWebActions.HandleRequest(ARequest: TRequest; AResponse: TResponse; Var Handled : Boolean);

Var
  A : TCustomWebAction;

begin
{$ifdef cgidebug}SendMethodEnter('FPWebActions.handlerequest');{$endif cgidebug}
  A:=GetRequestAction(ARequest);
  if Assigned(A) then
    begin
    FCurrentAction := A;
    (A as TFPWebAction).HandleRequest(ARequest,AResponse,Handled);
    end;
{$ifdef cgidebug}SendMethodExit('FPWebActions.handlerequest');{$endif cgidebug}
end;

procedure TFPWebActions.GetContent(ARequest: TRequest; Content: TStream;
  var Handled: Boolean);

Var
  A : TCustomWebAction;

begin
{$ifdef cgidebug}SendMethodEnter('WebActions.GetContent');{$endif cgidebug}
  A:=GetRequestAction(ARequest);
  If A is TFPWebAction then
   TFPWebAction(A).GetContent(ARequest,Content,Handled)
  else
    Raise EFPWebError.CreateFmt(SErrInvalidWebAction,[A.ClassName]);
{$ifdef cgidebug}SendMethodExit('WebActions.GetContent');{$endif cgidebug}
end;

end.

