more robust testing platform, #37340
authorMark Wells <mark@freeside.biz>
Tue, 26 Apr 2016 19:10:43 +0000 (12:10 -0700)
committerMark Wells <mark@freeside.biz>
Tue, 26 Apr 2016 19:10:51 +0000 (12:10 -0700)
FS/FS/Mason/StandaloneRequest.pm
FS/FS/Test.pm [new file with mode: 0644]
FS/t/suite/00-new_customer.t [new file with mode: 0755]
FS/t/suite/01-order_pkg.t [new file with mode: 0755]
FS/t/suite/02-bill_customer.t [new file with mode: 0755]
FS/t/suite/03-realtime_pay.t [new file with mode: 0755]
FS/t/suite/WRITING [new file with mode: 0644]
httemplate/elements/error.html
httemplate/elements/errorpage.html
httemplate/elements/header-full.html [new file with mode: 0644]
httemplate/elements/header.html

index a5e4dcb..e34a353 100644 (file)
@@ -20,4 +20,13 @@ sub new {
 
 }
 
+# fake this up for UI testing
+sub redirect {
+  my $self = shift;
+  if (scalar(@_)) {
+    $self->{_redirect} = shift;
+  }
+  return $self->{_redirect};
+}
+
 1;
diff --git a/FS/FS/Test.pm b/FS/FS/Test.pm
new file mode 100644 (file)
index 0000000..9854b94
--- /dev/null
@@ -0,0 +1,238 @@
+package FS::Test;
+
+use 5.006;
+use strict;
+use warnings FATAL => 'all';
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record;
+use URI;
+use URI::Escape;
+use Class::Accessor 'antlers';
+use Class::Load qw(load_class);
+use File::Spec;
+use HTML::Form;
+
+our $VERSION = '0.03';
+
+=head1 NAME
+
+Freeside testing suite
+
+=head1 SYNOPSIS
+
+  use Test::More 'tests' => 1;
+  use FS::Test;
+  my $FS = FS::Test->new;
+  $FS->post('/edit/cust_main.cgi', ... ); # form fields
+  ok( !$FS->error );
+
+=head1 PROPERTIES
+
+=over 4
+
+=item page
+
+The content of the most recent page fetched from the UI.
+
+=item redirect
+
+The redirect location (relative to the Freeside root) of the redirect
+returned from the UI, if there was one.
+
+=head1 CLASS METHODS
+
+=item new OPTIONS
+
+Creates a test session. OPTIONS may contain:
+
+- user: the Freeside test username [test]
+- base: the fake base URL for Mason to use [http://fake.freeside.biz]
+
+=cut
+
+has user      => ( is => 'rw' );
+has base      => ( is => 'ro' );
+has fs_interp => ( is => 'rw' );
+has path      => ( is => 'rw' );
+has page      => ( is => 'ro' );
+has error     => ( is => 'rw' );
+has dbh       => ( is => 'rw' );
+has redirect  => ( is => 'rw' );
+
+sub new {
+  my $class = shift;
+  my $self = {
+    user  => 'test',
+    page  => '',
+    error => '',
+    base  => 'http://fake.freeside.biz',
+    @_
+  };
+  $self->{base} = URI->new($self->{base});
+  bless $self;
+
+  adminsuidsetup($self->user);
+  load_class('FS::Mason');
+  $self->dbh( FS::UID::dbh() );
+
+  my ($fs_interp) = FS::Mason::mason_interps('standalone',
+    outbuf => \($self->{page})
+  );
+  $fs_interp->error_mode('fatal');
+  $fs_interp->error_format('brief');
+
+  $self->fs_interp( $fs_interp );
+
+  RT::LoadConfig();
+  RT::Init();
+
+  return $self;
+}
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item post PATH, PARAMS
+
+=item post FORM
+
+Submits a request to PATH, through the Mason UI, with arguments in PARAMS.
+This will be converted to a URL query string. Anything returned by the UI
+will be in the C<page()> property. 
+
+Alternatively, takes an L<HTML::Form> object (with fields filled in, via
+the C<param()> method) and submits it.
+
+=cut
+
+sub post {
+  my $self = shift;
+
+  # shut up, CGI
+  local $CGI::LIST_CONTEXT_WARN = 0;
+
+  my ($path, $query);
+  if ( UNIVERSAL::isa($_[0], 'HTML::Form') ) {
+    my $form = shift;
+    my $request = $form->make_request;
+    $path = $request->uri->path;
+    $query = $request->content;
+  } else {
+    $path = shift;
+    my @params = @_;
+    if (scalar(@params) == 0) {
+      # possibly path?query syntax, or else no query string at all
+      ($path, $query) = split('\?', $path);
+    } elsif (scalar(@params) == 1) {
+      $query = uri_escape($params[0]); # keyword style
+    } else {
+      while (@params) {
+        $query .= uri_escape(shift @params) . '=' .
+                  uri_escape(shift @params);
+        $query .= ';' if @params;
+      }
+    }
+  }
+  # remember which page this is
+  $self->path($path);
+
+  local $FS::Mason::Request::FSURL = $self->base->as_string;
+  local $FS::Mason::Request::QUERY_STRING = $query;
+  # because we're going to construct an actual CGI object in here
+  local $ENV{SERVER_NAME} = $self->base->host;
+  local $ENV{SCRIPT_NAME} = $self->base->path . $path;
+  local $@ = '';
+  my $mason_request = $self->fs_interp->make_request(comp => $path);
+  eval {
+    $mason_request->exec();
+  };
+
+  if ( $@ ) {
+    if ( ref $@ eq 'HTML::Mason::Exception' ) {
+      $self->error($@->message);
+    } else {
+      $self->error($@);
+    }
+  } elsif ( $mason_request->notes('error') ) {
+    $self->error($mason_request->notes('error'));
+  } else {
+    $self->error('');
+  }
+
+  if ( my $loc = $mason_request->redirect ) {
+    my $base = $self->base->as_string;
+    $loc =~ s/^$base//;
+    $self->redirect($loc);
+  } else {
+    $self->redirect('');
+  }
+  ''; # return error? HTTP status? something?
+}
+
+=item proceed
+
+If the last request returned a redirect, follow it.
+
+=cut
+
+sub proceed {
+  my $self = shift;
+  if ($self->redirect) {
+    $self->post($self->redirect);
+  }
+  # else do nothing
+}
+
+=item forms
+
+For the most recently returned page, returns a list of L<HTML::Form>s found.
+
+=cut
+
+sub forms {
+  my $self = shift;
+  my $formbase = $self->base->as_string . $self->path;
+  return HTML::Form->parse( $self->page, base => $formbase );
+}
+
+=item form NAME
+
+For the most recently returned page, returns an L<HTML::Form> object
+representing the form named NAME. You can then call methods like
+C<value(inputname, inputvalue)> to set the values of inputs on the form,
+and then pass the form object to L</post> to submit it.
+
+=cut
+
+sub form {
+  my $self = shift;
+  my $name = shift;
+  my ($form) = grep { $_->attr('name') eq $name } $self->forms;
+  $form;
+}
+
+=item qsearch ARGUMENTS
+
+Searches the database, like L<FS::Record::qsearch>.
+
+=item qsearchs ARGUMENTS
+
+Searches the database for a single record, like L<FS::Record::qsearchs>.
+
+=cut
+
+sub qsearch {
+  my $self = shift;
+  FS::Record::qsearch(@_);
+}
+
+sub qsearchs {
+  my $self = shift;
+  FS::Record::qsearchs(@_);
+}
+
+1; # End of FS::Test
diff --git a/FS/t/suite/00-new_customer.t b/FS/t/suite/00-new_customer.t
new file mode 100755 (executable)
index 0000000..8e86459
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+use FS::Test;
+use Test::More tests => 4;
+
+my $FS = FS::Test->new;
+# get the form
+$FS->post('/edit/cust_main.cgi');
+my $form = $FS->form('CustomerForm');
+
+my %params = (
+  residential_commercial  => 'Residential',
+  agentnum                => 1,
+  refnum                  => 1,
+  last                    => 'Customer',
+  first                   => 'New',
+  invoice_email           => 'newcustomer@fake.freeside.biz',
+  bill_address1           => '123 Example Street',
+  bill_address2           => 'Apt. Z',
+  bill_city               => 'Sacramento',
+  bill_state              => 'CA',
+  bill_zip                => '94901',
+  bill_country            => 'US',
+  bill_coord_auto         => 'Y',
+  daytime                 => '916-555-0100',
+  night                   => '916-555-0200',
+  ship_address1           => '125 Example Street',
+  ship_address2           => '3rd Floor',
+  ship_city               => 'Sacramento',
+  ship_state              => 'CA',
+  ship_zip                => '94901',
+  ship_country            => 'US',
+  ship_coord_auto         => 'Y',
+  invoice_ship_address    => 'Y',
+  postal_invoice          => 'Y',
+  billday                 => '1',
+  no_credit_limit         => 1,
+  # payment method
+  custpaybynum0_payby         => 'CARD',
+  custpaybynum0_payinfo       => '4012888888881881',
+  custpaybynum0_paydate_month => '12',
+  custpaybynum0_paydate_year  => '2020',
+  custpaybynum0_paycvv        => '123',
+  custpaybynum0_payname       => '',
+  custpaybynum0_weight        => 1,
+);
+foreach (keys %params) {
+  $form->value($_, $params{$_});
+}
+$FS->post($form);
+ok( $FS->error eq '' , 'form posted' );
+if (
+  ok($FS->redirect =~ m[^/view/cust_main.cgi\?(\d+)], 'new customer accepted')
+) {
+  my $custnum = $1;
+  my $cust = $FS->qsearchs('cust_main', { custnum => $1 });
+  isa_ok ( $cust, 'FS::cust_main' );
+  $FS->post($FS->redirect);
+  ok ( $FS->error eq '' , 'can view customer' );
+} else {
+  # try to display the error message, or if not, show everything
+  $FS->post($FS->redirect);
+  diag ($FS->error);
+  done_testing(2);
+}
+
+1;
diff --git a/FS/t/suite/01-order_pkg.t b/FS/t/suite/01-order_pkg.t
new file mode 100755 (executable)
index 0000000..1511350
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+
+use Test::More tests => 4;
+use FS::Test;
+use Date::Parse 'str2time';
+my $FS = FS::Test->new;
+
+# get the form
+$FS->post('/misc/order_pkg.html', custnum => 2);
+my $form = $FS->form('OrderPkgForm');
+
+# Customer #2 has three packages:
+# a $30 monthly prorate, a $90 monthly prorate, and a $25 annual prorate.
+# Next bill date on the monthly prorates is 2016-04-01.
+# Add a new package that will start billing on 2016-03-20 (to make prorate
+# behavior visible).
+
+my %params = (
+  pkgpart                 => 5,
+  quantity                => 1,
+  start                   => 'on_date',
+  start_date              => '03/20/2016',
+  package_comment0        => $0, # record the test we're executing
+);
+
+$form->find_input('start')->disabled(0); # JS
+foreach (keys %params) {
+  $form->value($_, $params{$_});
+}
+$FS->post($form);
+ok( $FS->error eq '' , 'form posted' );
+if (
+   ok( $FS->page =~ m[location = '.*/view/cust_main.cgi.*\#cust_pkg(\d+)'],
+      'new package accepted' )
+) {
+ # on success, sends us back to cust_main view with #cust_pkg$pkgnum
+  # but with an in-page javascript redirect
+  my $pkg = $FS->qsearchs('cust_pkg', { pkgnum => $1 });
+  isa_ok( $pkg, 'FS::cust_pkg' );
+  ok($pkg->start_date == str2time('2016-03-20'), 'start date set');
+} else {
+  # try to display the error message, or if not, show everything
+  $FS->post($FS->redirect);
+  diag ($FS->error);
+  done_testing(2);
+}
+
+1;
+
diff --git a/FS/t/suite/02-bill_customer.t b/FS/t/suite/02-bill_customer.t
new file mode 100755 (executable)
index 0000000..0afffaa
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+
+use FS::Test;
+use Test::More tests => 6;
+use Test::MockTime 'set_fixed_time';
+use Date::Parse 'str2time';
+use FS::cust_main;
+
+my $FS = FS::Test->new;
+
+# After test 01: cust#2 has a package set to bill on 2016-03-20.
+# Set local time.
+my $date = '2016-03-20';
+set_fixed_time(str2time($date));
+my $cust_main = FS::cust_main->by_key(2);
+my @return;
+
+# Bill the customer.
+my $error = $cust_main->bill( return_bill => \@return );
+ok($error eq '', "billed on $date") or diag($error);
+
+# should be an invoice now
+my $cust_bill = $return[0];
+isa_ok($cust_bill, 'FS::cust_bill');
+
+# $60/month * (30 days - 19 days)/30 days = $42
+ok( $cust_bill->charged == 42.00, 'prorated first month correctly' );
+
+# the package bill date should now be 2016-04-01
+my @lineitems = $cust_bill->cust_bill_pkg;
+ok( scalar(@lineitems) == 1, 'one package was billed' );
+my $pkg = $lineitems[0]->cust_pkg;
+ok( $pkg->status eq 'active', 'package is now active' );
+ok( $pkg->bill == str2time('2016-04-01'), 'package bill date set correctly' );
+
+1;
diff --git a/FS/t/suite/03-realtime_pay.t b/FS/t/suite/03-realtime_pay.t
new file mode 100755 (executable)
index 0000000..17456bb
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+use FS::Test;
+use Test::More tests => 2;
+use FS::cust_main;
+
+my $FS = FS::Test->new;
+
+# In the stock database, cust#5 has open invoices
+my $cust_main = FS::cust_main->by_key(5);
+my $balance = $cust_main->balance;
+ok( $balance > 10.00, 'customer has an outstanding balance of more than $10.00' );
+
+# Get the payment form
+$FS->post('/misc/payment.cgi?payby=CARD;custnum=5');
+my $form = $FS->form('OneTrueForm');
+$form->value('amount'       => '10.00');
+$form->value('custpaybynum' => '');
+$form->value('payinfo'      => '4012888888881881');
+$form->value('month'        => '01');
+$form->value('year'         => '2020');
+# payname and location fields should already be set
+$form->value('save'         => 1);
+$form->value('auto'         => 1);
+$FS->post($form);
+
+# on success, gives a redirect to the payment receipt
+my $paynum;
+if ($FS->redirect =~ m[^/view/cust_pay.html\?(\d+)]) {
+  pass('payment processed');
+  $paynum = $1;
+} elsif ( $FS->error ) {
+  fail('payment rejected');
+  diag ( $FS->error );
+} else {
+  fail('unknown result');
+  diag ( $FS->page );
+}
+
+1;
diff --git a/FS/t/suite/WRITING b/FS/t/suite/WRITING
new file mode 100644 (file)
index 0000000..d9421cc
--- /dev/null
@@ -0,0 +1,93 @@
+WRITING TESTS
+
+Load the test database (kept in FS-Test/share/test.sql for now). This has
+a large set of customers in a known initial state.  You can login through
+the web interface as "admin"/"admin" to examine the state of things and plan
+your test.
+
+The test scripts now have access to BOTH sides of the web interface, so you
+can create an object through the UI and then examine its internal
+properties, etc.
+
+  use Test::More tests => 1;
+  use FS::Test;
+  my $FS = FS::Test->new;
+
+$FS has qsearch and qsearchs methods for finding objects directly. You can
+do anything with those objects that Freeside backend code could normally do.
+For example, this will bill a customer:
+
+  my $cust = $FS->qsearchs('cust_main', { custnum => 52 });
+  my $error = $cust->bill;
+
+TESTING UI INTERACTION
+
+To fetch a page from the UI, use the post() method:
+
+  $FS->post('/view/cust_main.cgi?52');
+  ok( $FS->error eq '', 'fetched customer view' ) or diag($FS->error);
+  ok( $FS->page =~ /Customer, New/, 'customer is named "Customer, New"' );
+
+To simulate a user filling in and submitting a form, first fetch the form,
+and select it by name:
+
+  $FS->post('/edit/svc_acct.cgi?98');
+  my $form = $FS->form('OneTrueForm');
+
+then fill it in and submit it:
+
+  $form->value('clear_password', '1234abcd');
+  $FS->post($form);
+
+and examine the result:
+
+  my $svc_acct = $FS->qsearch('svc_acct', { svcnum => 98 });
+  ok( $svc_acct->_password eq '1234abcd', 'password was changed' );
+
+TESTING UI FLOW (EDIT/PROCESS/VIEW SEQUENCE)
+
+Forms for editing records will post to a processing page. $FS->post($form)
+handles this. The processing page will usually redirect back to the view
+page on success, and back to the edit form with an error on failure.
+Determine which kind of redirect it is. If it's a redirect to the edit form,
+you need to follow it to report the error.
+
+  if ( $FS->redirect =~ m[^/view/svc_acct.cgi] ) {
+
+    pass('redirected to view page');
+
+  } elsif ( $FS->redirect =~ m[^/edit/svc_acct.cgi] ) {
+
+    fail('redirected back to edit form');
+    $FS->post($FS->redirect);
+    diag($FS->error);
+
+  } else {
+
+    fail('unsure what happened');
+    diag($FS->page);
+
+  }
+
+RUNNING TESTS AT A SPECIFIC DATE
+
+Important for testing package billing. Test::MockTime provides the
+set_fixed_time() function, which will freeze the time returned by the time()
+function at a specific value. I recommend giving it a unix timestamp rather
+than a date string to avoid any confusion about time zones.
+
+Note that FS::Cron::bill and some other parts of the system look at the $^T
+variable (the time that the current program started running). You can
+override that by just assigning to the variable.
+
+Customers in the test database are billed up through Mar 1 2016. This will
+bill a customer for the next month after that:
+
+  use Test::MockTime qw(set_fixed_time);
+  use Date::Parse qw(str2time);
+
+  my $cust = $FS->qsearchs('cust_main', { custnum => 52 });
+  set_fixed_time( str2time('2016-04-01') );
+  $cust->bill;
+
+
index f65785d..f9664bd 100644 (file)
@@ -1,4 +1,5 @@
 % if ( $cgi->param('error') ) { 
+%   $m->notes('error', $cgi->param('error'));
   <FONT SIZE="+1" COLOR="#ff0000"><% mt("Error: [_1]", $cgi->param('error')) |h %></FONT>
   <BR><BR>
 % } 
index 0f9808d..7d66e7c 100644 (file)
@@ -1,10 +1,11 @@
+% my $error = shift;
+% $m->notes('error', $error);
 <& /elements/header.html, mt("Error") &>
 
 % while (@_) {
 
-<P><FONT SIZE="+1" COLOR="#ff0000"><% shift |h %></FONT>
+<P><FONT SIZE="+1" COLOR="#ff0000"><% $error |h %></FONT>
 
 %}
-
 % $m->flush_buffer();
 % $HTML::Mason::Commands::m->abort();
diff --git a/httemplate/elements/header-full.html b/httemplate/elements/header-full.html
new file mode 100644 (file)
index 0000000..699f82c
--- /dev/null
@@ -0,0 +1,236 @@
+<%doc>
+
+Example:
+
+  <& /elements/header.html',
+       {
+         'title'     => 'Title',
+         'menubar'   => \@menubar,
+         'etc'       => '', #included in <BODY> tag, for things like onLoad=
+         'head'      => '', #included before closing </HEAD> tag
+         'nobr'      => 0,  #1 for no <BR><BR> after the title
+         'no_jquery' => #for use from RT, which loads its own
+       }
+  &>
+
+  %#old-style
+  <& /elements/header.html, 'Title', $menubar, $etc, $head &>
+
+</%doc>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+%#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+%# above is what RT declares, should we switch now? hopefully no glitches result
+%# or just fuck it, XHTML died anyway, HTML 5 or bust?
+<HTML>
+  <HEAD>
+    <TITLE>
+      <% encode_entities($title) || $title_noescape |n %>
+    </TITLE>
+    <!-- per RT, to prevent IE compatibility mode -->
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <!-- The X-UA-Compatible <meta> tag above must be very early in <head> -->
+    <META HTTP-Equiv="Cache-Control" Content="no-cache">
+    <META HTTP-Equiv="Pragma" Content="no-cache">
+    <META HTTP-Equiv="Expires" Content="0"> 
+% if ( $mobile ) {
+    <META NAME="viewport" content="width=device-width height=device-height user-scalable=yes">
+% }
+
+    <% include('menu.html', 'freeside_baseurl' => $fsurl,
+                            'position'         => $menu_position,
+                            'nocss'            => $nocss,
+                            'mobile'           => $mobile,
+              ) |n
+    %>
+
+%   unless ( $no_jquery ) {
+      <link rel="stylesheet" href="<% $fsurl %>elements/jquery-ui.min.css">
+      <SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT>
+      <SCRIPT SRC="<% $fsurl %>elements/jquery-ui.min.js"></SCRIPT>
+%   }
+    <% include('init_overlib.html') |n %>
+    <% include('rs_init_object.html') |n %>
+
+    <% $head |n %>
+
+%# announce our base path, and the Mason comp path of this page
+  <script type="text/javascript">
+  window.fsurl = <% $fsurl |js_string %>;
+  window.request_comp_path = <% $m->request_comp->path |js_string %>;
+  </script>
+
+  </HEAD>
+  <BODY BGCOLOR="#f8f8f8" <% $etc |n %> STYLE="margin-top:0; margin-bottom:0; margin-left:0px; margin-right:0px">
+    <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0px; padding-right:4px" CLASS="fshead">
+      <tr>
+        <td BGCOLOR="#ffffff"><% $company_url ? qq(<A HREF="$company_url">) : '' |n %><IMG BORDER=0 ALT="freeside" HEIGHT="36" SRC="<%$fsurl%>view/REAL_logo.cgi"><% $company_url ? '</A>' : '' |n %></td>
+        <td align=left BGCOLOR="#ffffff"> <!-- valign="top" -->
+          <font size=6><% $company_name || 'ExampleCo' %></font>
+        </td>
+        <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% $FS::CurrentUser::CurrentUser->username |h %>&nbsp;</b> <FONT SIZE="-2"><a href="<%$fsurl%>loginout/logout.html">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a>
+%         if ( $conf->config("ticket_system")
+%              && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) {
+            | <a href="<%$fsurl%>rt/Prefs/Other.html" STYLE="color: #000000">Ticketing preferences</a>
+%         }
+          <BR></FONT>
+        </td>
+      </tr>
+    </table>
+
+    <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0>
+
+<link href="<%$fsurl%>elements/freeside-menu.css" type="text/css" rel="stylesheet">
+
+% if ( $menu_position eq 'top' ) {
+
+      <TR CLASS="fsmenubar">
+
+%       if ( $mobile ) {
+
+        <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd">
+          <SCRIPT TYPE="text/javascript">
+            document.write(myBar.toString());
+          </SCRIPT>
+        </TD>
+        <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd">
+            <% include('searchbar-combined.html') |n %>
+        </TD>
+
+%       } else {
+
+        <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd">
+          <SCRIPT TYPE="text/javascript">
+            document.write(myBar);
+          </SCRIPT>
+        </TD>
+
+      </TR>
+
+      <TR CLASS="fssearchbar">
+
+        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px">
+          <% include('searchbar-prospect.html') |n %>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px">
+          <% include('searchbar-cust_main.html') |n %>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="center">
+          <% include('searchbar-address2.html') |n %>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right">
+          <% include('searchbar-cust_bill.html') |n %>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px">
+          <% include('searchbar-cust_svc.html') |n %>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px;padding-right:2px">
+          <% include('searchbar-ticket.html') |n %>
+        </TD>
+%       }
+
+      </TR>
+    </TABLE>
+
+% } else { #$menu_position eq 'left'
+
+      <TR CLASS="fsmenubar">
+
+        <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd">
+        </TD>
+
+      </TR>
+
+% }
+
+
+    <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4>
+
+      <TR HEIGHT="100%">
+
+% if ( $menu_position eq 'left' ) {
+
+        <TD BGCOLOR="#dddddd" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right" CLASS="fsmenubar">
+          <SCRIPT TYPE="text/javascript">
+            document.write(myBar);
+          </SCRIPT>
+
+          <BR>
+          <% include('searchbar-prospect.html') |n %>
+          <% include('searchbar-cust_main.html') |n %>
+          <% include('searchbar-address2.html') |n %>
+          <% include('searchbar-cust_bill.html') |n %>
+          <% include('searchbar-cust_svc.html') |n %>
+          <% include('searchbar-ticket.html') |n %>
+
+        </TD>
+
+% } else { #$menu_position eq 'top'
+    <BR>
+% }
+%# page content starts here
+        <TD CLASS="background" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> -->
+
+          <H1>
+            <% $title_noescape || encode_entities($title) %>
+          </H1>
+
+% unless ( $nobr ) {
+          <BR>
+% }
+
+          <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %>
+
+<%init>
+
+my( $title, $title_noescape, $menubar, $etc, $head ) = ( '', '', '', '', '' );
+my( $nobr, $nocss, $no_jquery ) = ( 0, 0, 0 );
+
+my $mobile;
+
+if ( ref($_[0]) ) {
+  my $opt = shift;
+  $title   = $opt->{title};
+  $title_noescape = $opt->{title_noescape};
+  $menubar    = $opt->{menubar};
+  $etc        = $opt->{etc};
+  $head       = $opt->{head};
+  $nobr       = $opt->{nobr};
+  $nocss      = $opt->{nocss};
+  $mobile     = $opt->{mobile};
+  $no_jquery  = $opt->{no_jquery};
+} else {
+  ($title, $menubar) = ( shift, shift );
+  $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+  $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+}
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position')
+                    || 'top'; #new default for 1.9
+
+if ( !defined($mobile) ) {
+  $mobile = $curuser->option('mobile_menu',1) && FS::UI::Web::is_mobile();
+}
+if ( $cgi->param('mobile') =~ /^(\d)$/ ) { # allow client to override
+  $mobile = $1;
+}
+
+my($company_name, $company_url);
+my @agentnums = $curuser->agentnums;
+if ( scalar(@agentnums) == 1 ) {
+  $company_name = $conf->config('company_name', $agentnums[0] );
+  $company_url  = $conf->config('company_url',  $agentnums[0] );
+} else {
+  $company_name = $conf->config('company_name');
+  $company_url  = $conf->config('company_url');
+}
+
+</%init>
index 699f82c..c6b10e3 100644 (file)
@@ -1,236 +1,6 @@
-<%doc>
-
-Example:
-
-  <& /elements/header.html',
-       {
-         'title'     => 'Title',
-         'menubar'   => \@menubar,
-         'etc'       => '', #included in <BODY> tag, for things like onLoad=
-         'head'      => '', #included before closing </HEAD> tag
-         'nobr'      => 0,  #1 for no <BR><BR> after the title
-         'no_jquery' => #for use from RT, which loads its own
-       }
-  &>
-
-  %#old-style
-  <& /elements/header.html, 'Title', $menubar, $etc, $head &>
-
-</%doc>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-%#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-%# above is what RT declares, should we switch now? hopefully no glitches result
-%# or just fuck it, XHTML died anyway, HTML 5 or bust?
-<HTML>
-  <HEAD>
-    <TITLE>
-      <% encode_entities($title) || $title_noescape |n %>
-    </TITLE>
-    <!-- per RT, to prevent IE compatibility mode -->
-    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
-    <!-- The X-UA-Compatible <meta> tag above must be very early in <head> -->
-    <META HTTP-Equiv="Cache-Control" Content="no-cache">
-    <META HTTP-Equiv="Pragma" Content="no-cache">
-    <META HTTP-Equiv="Expires" Content="0"> 
-% if ( $mobile ) {
-    <META NAME="viewport" content="width=device-width height=device-height user-scalable=yes">
+% # for testing, disable the page menus/search boxes
+% if ( $FS::CurrentUser::CurrentUser->option('header-minimal') ) {
+<& header-minimal.html, @_ &>
+% } else {
+<& header-full.html, @_ &>
 % }
-
-    <% include('menu.html', 'freeside_baseurl' => $fsurl,
-                            'position'         => $menu_position,
-                            'nocss'            => $nocss,
-                            'mobile'           => $mobile,
-              ) |n
-    %>
-
-%   unless ( $no_jquery ) {
-      <link rel="stylesheet" href="<% $fsurl %>elements/jquery-ui.min.css">
-      <SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT>
-      <SCRIPT SRC="<% $fsurl %>elements/jquery-ui.min.js"></SCRIPT>
-%   }
-    <% include('init_overlib.html') |n %>
-    <% include('rs_init_object.html') |n %>
-
-    <% $head |n %>
-
-%# announce our base path, and the Mason comp path of this page
-  <script type="text/javascript">
-  window.fsurl = <% $fsurl |js_string %>;
-  window.request_comp_path = <% $m->request_comp->path |js_string %>;
-  </script>
-
-  </HEAD>
-  <BODY BGCOLOR="#f8f8f8" <% $etc |n %> STYLE="margin-top:0; margin-bottom:0; margin-left:0px; margin-right:0px">
-    <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0px; padding-right:4px" CLASS="fshead">
-      <tr>
-        <td BGCOLOR="#ffffff"><% $company_url ? qq(<A HREF="$company_url">) : '' |n %><IMG BORDER=0 ALT="freeside" HEIGHT="36" SRC="<%$fsurl%>view/REAL_logo.cgi"><% $company_url ? '</A>' : '' |n %></td>
-        <td align=left BGCOLOR="#ffffff"> <!-- valign="top" -->
-          <font size=6><% $company_name || 'ExampleCo' %></font>
-        </td>
-        <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% $FS::CurrentUser::CurrentUser->username |h %>&nbsp;</b> <FONT SIZE="-2"><a href="<%$fsurl%>loginout/logout.html">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a>
-%         if ( $conf->config("ticket_system")
-%              && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) {
-            | <a href="<%$fsurl%>rt/Prefs/Other.html" STYLE="color: #000000">Ticketing preferences</a>
-%         }
-          <BR></FONT>
-        </td>
-      </tr>
-    </table>
-
-    <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0>
-
-<link href="<%$fsurl%>elements/freeside-menu.css" type="text/css" rel="stylesheet">
-
-% if ( $menu_position eq 'top' ) {
-
-      <TR CLASS="fsmenubar">
-
-%       if ( $mobile ) {
-
-        <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd">
-          <SCRIPT TYPE="text/javascript">
-            document.write(myBar.toString());
-          </SCRIPT>
-        </TD>
-        <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd">
-            <% include('searchbar-combined.html') |n %>
-        </TD>
-
-%       } else {
-
-        <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd">
-          <SCRIPT TYPE="text/javascript">
-            document.write(myBar);
-          </SCRIPT>
-        </TD>
-
-      </TR>
-
-      <TR CLASS="fssearchbar">
-
-        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px">
-          <% include('searchbar-prospect.html') |n %>
-        </TD>
-
-        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px">
-          <% include('searchbar-cust_main.html') |n %>
-        </TD>
-
-        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="center">
-          <% include('searchbar-address2.html') |n %>
-        </TD>
-
-        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right">
-          <% include('searchbar-cust_bill.html') |n %>
-        </TD>
-
-        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px">
-          <% include('searchbar-cust_svc.html') |n %>
-        </TD>
-
-        <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px;padding-right:2px">
-          <% include('searchbar-ticket.html') |n %>
-        </TD>
-%       }
-
-      </TR>
-    </TABLE>
-
-% } else { #$menu_position eq 'left'
-
-      <TR CLASS="fsmenubar">
-
-        <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd">
-        </TD>
-
-      </TR>
-
-% }
-
-
-    <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4>
-
-      <TR HEIGHT="100%">
-
-% if ( $menu_position eq 'left' ) {
-
-        <TD BGCOLOR="#dddddd" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right" CLASS="fsmenubar">
-          <SCRIPT TYPE="text/javascript">
-            document.write(myBar);
-          </SCRIPT>
-
-          <BR>
-          <% include('searchbar-prospect.html') |n %>
-          <% include('searchbar-cust_main.html') |n %>
-          <% include('searchbar-address2.html') |n %>
-          <% include('searchbar-cust_bill.html') |n %>
-          <% include('searchbar-cust_svc.html') |n %>
-          <% include('searchbar-ticket.html') |n %>
-
-        </TD>
-
-% } else { #$menu_position eq 'top'
-    <BR>
-% }
-%# page content starts here
-        <TD CLASS="background" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> -->
-
-          <H1>
-            <% $title_noescape || encode_entities($title) %>
-          </H1>
-
-% unless ( $nobr ) {
-          <BR>
-% }
-
-          <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %>
-
-<%init>
-
-my( $title, $title_noescape, $menubar, $etc, $head ) = ( '', '', '', '', '' );
-my( $nobr, $nocss, $no_jquery ) = ( 0, 0, 0 );
-
-my $mobile;
-
-if ( ref($_[0]) ) {
-  my $opt = shift;
-  $title   = $opt->{title};
-  $title_noescape = $opt->{title_noescape};
-  $menubar    = $opt->{menubar};
-  $etc        = $opt->{etc};
-  $head       = $opt->{head};
-  $nobr       = $opt->{nobr};
-  $nocss      = $opt->{nocss};
-  $mobile     = $opt->{mobile};
-  $no_jquery  = $opt->{no_jquery};
-} else {
-  ($title, $menubar) = ( shift, shift );
-  $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
-  $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
-}
-
-my $conf = new FS::Conf;
-
-my $curuser = $FS::CurrentUser::CurrentUser;
-
-my $menu_position = $curuser->option('menu_position')
-                    || 'top'; #new default for 1.9
-
-if ( !defined($mobile) ) {
-  $mobile = $curuser->option('mobile_menu',1) && FS::UI::Web::is_mobile();
-}
-if ( $cgi->param('mobile') =~ /^(\d)$/ ) { # allow client to override
-  $mobile = $1;
-}
-
-my($company_name, $company_url);
-my @agentnums = $curuser->agentnums;
-if ( scalar(@agentnums) == 1 ) {
-  $company_name = $conf->config('company_name', $agentnums[0] );
-  $company_url  = $conf->config('company_url',  $agentnums[0] );
-} else {
-  $company_name = $conf->config('company_name');
-  $company_url  = $conf->config('company_url');
-}
-
-</%init>