discount reporting, RT#6679
[freeside.git] / FS / FS / Mason.pm
1 package FS::Mason;
2
3 use strict;
4 use vars qw( @ISA @EXPORT_OK $addl_handler_use );
5 use Exporter;
6 use File::Slurp qw( slurp );
7 use HTML::Mason 1.27; #http://www.masonhq.com/?ApacheModPerl2Redirect
8 use HTML::Mason::Interp;
9 use HTML::Mason::Compiler::ToObject;
10
11 @ISA = qw( Exporter );
12 @EXPORT_OK = qw( mason_interps );
13
14 =head1 NAME
15
16 FS::Mason - Initialize the Mason environment
17
18 =head1 SYNOPSIS
19
20   use FS::Mason qw( mason_interps );
21
22   my( $fs_interp, $rt_interp ) = mason_interps('apache');
23
24   #OR
25
26   my( $fs_interp, $rt_interp ) = mason_interps('standalone'); #XXX name?
27
28 =head1 DESCRIPTION
29
30 Initializes the Mason environment, loads all Freeside and RT libraries, etc.
31
32 =cut
33
34 $addl_handler_use = '';
35 my $addl_handler_use_file = '%%%FREESIDE_CONF%%%/addl_handler_use.pl';
36 if ( -e $addl_handler_use_file ) {
37   $addl_handler_use = slurp( $addl_handler_use_file );
38 }
39
40 # List of modules that you want to use from components (see Admin
41 # manual for details)
42 {
43   package HTML::Mason::Commands;
44
45   use strict;
46   use vars qw( %session );
47   use CGI 3.29 qw(-private_tempfiles); #3.29 to fix RT attachment problems
48
49   #breaks quick payment entry
50   #http://rt.cpan.org/Public/Bug/Display.html?id=37365
51   die "CGI.pm v3.38 is broken, use any other version >= 3.29".
52       " (Debian 5.0?  aptitude remove libcgi-pm-perl)"
53     if $CGI::VERSION == 3.38;
54
55   #use CGI::Carp qw(fatalsToBrowser);
56   use CGI::Cookie;
57   use List::Util qw( max min );
58   use Data::Dumper;
59   use Date::Format;
60   use Date::Parse;
61   use Time::Local;
62   use Time::HiRes;
63   use Time::Duration;
64   use DateTime;
65   use DateTime::Format::Strptime;
66   use Lingua::EN::Inflect qw(PL);
67   use Tie::IxHash;
68   use URI;
69   use URI::Escape;
70   use HTML::Entities;
71   use HTML::TreeBuilder;
72   use HTML::FormatText;
73   use JSON;
74   use MIME::Base64;
75   use IO::Handle;
76   use IO::File;
77   use IO::Scalar;
78   #not actually using this yet anyway...# use IPC::Run3 0.036;
79   use Net::Whois::Raw qw(whois);
80   if ( $] < 5.006 ) {
81     eval "use Net::Whois::Raw 0.32 qw(whois)";
82     die $@ if $@;
83   }
84   use Text::CSV_XS;
85   use Spreadsheet::WriteExcel;
86   use Business::CreditCard 0.30; #for mask-aware cardtype()
87   use NetAddr::IP;
88   use Net::Ping;
89   use Net::Ping::External;
90   #if CPAN #7815 ever gets fixed# if ( $Net::Ping::External::VERSION <= 0.12 )
91   {
92     no warnings 'redefine';
93     eval 'sub Net::Ping::External::_ping_linux { 
94             my %args = @_;
95             my $command = "ping -s $args{size} -c $args{count} -w $args{timeout} $args{host}";
96             return Net::Ping::External::_ping_system($command, 0);
97           }
98          ';
99     die $@ if $@;
100   }
101   use String::Approx qw(amatch);
102   use Chart::LinesPoints;
103   use Chart::Mountain;
104   use Color::Scheme;
105   use HTML::Widgets::SelectLayers 0.07; #should go away in favor of
106                                         #selectlayers.html
107   use Locale::Country;
108   use Business::US::USPS::WebTools::AddressStandardization;
109   use FS;
110   use FS::UID qw( getotaker dbh datasrc driver_name );
111   use FS::Record qw( qsearch qsearchs fields dbdef
112                     str2time_sql str2time_sql_closing
113                    );
114   use FS::Conf;
115   use FS::CGI qw(header menubar table itable ntable idiot
116                  eidiot myexit http_header);
117   use FS::UI::Web qw(svc_url);
118   use FS::UI::Web::small_custview qw(small_custview);
119   use FS::UI::bytecount;
120   use FS::Msgcat qw(gettext geterror);
121   use FS::Misc qw( send_email send_fax
122                    states_hash counties cities state_label
123                  );
124   use FS::Misc::eps2png qw( eps2png );
125   use FS::Report::Table::Monthly;
126   use FS::TicketSystem;
127   use FS::Tron qw( tron_lint );
128
129   use FS::agent;
130   use FS::agent_type;
131   use FS::domain_record;
132   use FS::cust_bill;
133   use FS::cust_bill_pay;
134   use FS::cust_credit;
135   use FS::cust_credit_bill;
136   use FS::cust_main qw(smart_search);
137   use FS::cust_main::Import;
138   use FS::cust_main_county;
139   use FS::cust_location;
140   use FS::cust_pay;
141   use FS::cust_pkg;
142   use FS::part_pkg_taxclass;
143   use FS::cust_pkg_reason;
144   use FS::cust_refund;
145   use FS::cust_credit_refund;
146   use FS::cust_pay_refund;
147   use FS::cust_svc;
148   use FS::nas;
149   use FS::part_bill_event;
150   use FS::part_event;
151   use FS::part_event_condition;
152   use FS::part_pkg;
153   use FS::part_referral;
154   use FS::part_svc;
155   use FS::part_svc_router;
156   use FS::part_virtual_field;
157   use FS::pay_batch;
158   use FS::pkg_svc;
159   use FS::port;
160   use FS::queue qw(joblisting);
161   use FS::raddb;
162   use FS::session;
163   use FS::svc_acct;
164   use FS::svc_acct_pop qw(popselector);
165   use FS::acct_rt_transaction;
166   use FS::svc_domain;
167   use FS::svc_forward;
168   use FS::svc_www;
169   use FS::router;
170   use FS::addr_block;
171   use FS::svc_broadband;
172   use FS::svc_external;
173   use FS::type_pkgs;
174   use FS::part_export;
175   use FS::part_export_option;
176   use FS::export_svc;
177   use FS::msgcat;
178   use FS::rate;
179   use FS::rate_region;
180   use FS::rate_prefix;
181   use FS::rate_detail;
182   use FS::usage_class;
183   use FS::payment_gateway;
184   use FS::agent_payment_gateway;
185   use FS::XMLRPC;
186   use FS::payby;
187   use FS::cdr;
188   use FS::cdr_batch;
189   use FS::inventory_class;
190   use FS::inventory_item;
191   use FS::pkg_category;
192   use FS::pkg_class;
193   use FS::access_user;
194   use FS::access_user_pref;
195   use FS::access_group;
196   use FS::access_usergroup;
197   use FS::access_groupagent;
198   use FS::access_right;
199   use FS::AccessRight;
200   use FS::svc_phone;
201   use FS::phone_device;
202   use FS::part_device;
203   use FS::reason_type;
204   use FS::reason;
205   use FS::cust_main_note;
206   use FS::tax_class;
207   use FS::cust_tax_location;
208   use FS::part_pkg_taxproduct;
209   use FS::part_pkg_taxoverride;
210   use FS::part_pkg_taxrate;
211   use FS::tax_rate;
212   use FS::part_pkg_report_option;
213   use FS::cust_attachment;
214   use FS::h_cust_pkg;
215   use FS::h_svc_acct;
216   use FS::h_svc_broadband;
217   use FS::h_svc_domain;
218   #use FS::h_domain_record;
219   use FS::h_svc_external;
220   use FS::h_svc_forward;
221   use FS::h_svc_phone;
222   #use FS::h_phone_device;
223   use FS::h_svc_www;
224   use FS::cust_statement;
225   use FS::cust_class;
226   use FS::cust_category;
227   use FS::prospect_main;
228   use FS::contact;
229   use FS::svc_pbx;
230   use FS::discount;
231   use FS::cust_pkg_discount;
232   use FS::cust_bill_pkg_discount;
233   # Sammath Naur
234
235   if ( $FS::Mason::addl_handler_use ) {
236     eval $FS::Mason::addl_handler_use;
237     die $@ if $@;
238   }
239
240   if ( %%%RT_ENABLED%%% ) {
241     eval '
242       use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
243       use vars qw($Nobody $SystemUser);
244       use RT;
245       use RT::Tickets;
246       use RT::Transactions;
247       use RT::Users;
248       use RT::CurrentUser;
249       use RT::Templates;
250       use RT::Queues;
251       use RT::ScripActions;
252       use RT::ScripConditions;
253       use RT::Scrips;
254       use RT::Groups;
255       use RT::GroupMembers;
256       use RT::CustomFields;
257       use RT::CustomFieldValues;
258       use RT::ObjectCustomFieldValues;
259
260       #blah.  manually updated from RT::Interface::Web::Handler
261       use RT::Interface::Web;
262       use MIME::Entity;
263       use Text::Wrapper;
264       use Time::ParseDate;
265       use Time::HiRes;
266       use HTML::Scrubber;
267
268       #blah.  not even in RT::Interface::Web::Handler, just in 
269       #html/NoAuth/css/dhandler and rt-test-dependencies.  ask for it here
270       #to throw a real error instead of just a mysterious unstyled RT
271       use CSS::Squish 0.06;
272
273       use RT::Interface::Web::Request;
274
275       #slow, unreliable, segfaults and is optional
276       #see rt/html/Ticket/Elements/ShowTransactionAttachments
277       #use Text::Quoted;
278
279       #?#use File::Path qw( rmtree );
280       #?#use File::Glob qw( bsd_glob );
281       #?#use File::Spec::Unix;
282
283     ';
284     die $@ if $@;
285   }
286
287   *CGI::redirect = sub {
288     my $self = shift;
289     my $cookie = '';
290     if ( $_[0] eq '-cookie' ) { #this isn't actually used at the moment
291       (my $x, $cookie) = (shift, shift);
292       $HTML::Mason::r->err_headers_out->add( 'Set-cookie' => $cookie );
293     }
294     my $location = shift;
295
296     use vars qw($m);
297
298     # false laziness w/below
299     if ( defined(@DBIx::Profile::ISA) ) {
300
301       if ( $FS::CurrentUser::CurrentUser->option('show_db_profile') ) {
302
303         #profiling redirect
304
305         my $page =
306           qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
307           '<BR><BR><PRE>'.
308             ( UNIVERSAL::can(dbh, 'sprintProfile')
309                 ? encode_entities(dbh->sprintProfile())
310                 : 'DBIx::Profile missing sprintProfile method;'.
311                   'unpatched or too old?'                        ).
312           #"\n\n". &sprintAutoProfile().  '</PRE>'.
313           "\n\n".                         '</PRE>'.
314           '</BODY></HTML>';
315
316
317         dbh->{'private_profile'} = {};
318         return $page;
319
320       } else {
321
322         #clear db profile, but normal redirect
323         dbh->{'private_profile'} = {};
324         $m->redirect($location);
325         '';
326
327       }
328
329     } else { #normal redirect
330
331       $m->redirect($location);
332       '';
333
334     }
335
336   };
337   
338   sub include {
339     use vars qw($m);
340     $m->scomp(@_);
341   }
342
343   sub errorpage {
344     use vars qw($m);
345     $m->comp('/elements/errorpage.html', @_);
346   }
347
348   sub redirect {
349     my( $location ) = @_;
350     use vars qw($m);
351     $m->clear_buffer;
352     #false laziness w/above
353     if ( defined(@DBIx::Profile::ISA) ) {
354
355       if ( $FS::CurrentUser::CurrentUser->option('show_db_profile') ) {
356
357         #profiling redirect
358
359         $m->print(
360           qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
361           '<BR><BR><PRE>'.
362             ( UNIVERSAL::can(dbh, 'sprintProfile')
363                 ? encode_entities(dbh->sprintProfile())
364                 : 'DBIx::Profile missing sprintProfile method;'.
365                   'unpatched or too old?'                        ).
366           #"\n\n". &sprintAutoProfile().  '</PRE>'.
367           "\n\n".                         '</PRE>'.
368           '</BODY></HTML>'
369         );
370
371         dbh->{'private_profile'} = {};
372
373       } else {
374
375         #clear db profile, but normal redirect
376         dbh->{'private_profile'} = {};
377         $m->redirect($location);
378
379       }
380
381     } else { #normal redirect
382
383       $m->redirect($location);
384
385     }
386
387   }
388
389 } # end package HTML::Mason::Commands;
390
391 =head1 SUBROUTINE
392
393 =over 4
394
395 =item mason_interps [ MODE [ OPTION => VALUE ... ] ]
396
397 Returns a list consisting of two HTML::Mason::Interp objects, the first for
398 Freeside pages, and the second for RT pages.
399
400 MODE can be 'apache' or 'standalone'.  If not specified, defaults to 'apache'.
401
402 Options and values can be passed following mode.  Currently available options
403 are:
404
405 I<outbuf> should be set to a scalar reference in standalone mode.
406
407 =cut
408
409 sub mason_interps {
410   my $mode = shift || 'apache';
411   my %opt = @_;
412
413   #my $request_class = 'HTML::Mason::Request'.
414                       #( $mode eq 'apache' ? '::ApacheHandler' : '' );
415   my $request_class = 'FS::Mason::Request';
416
417   #not entirely sure it belongs here, but what the hey
418   if ( %%%RT_ENABLED%%% && $mode ne 'standalone' ) {
419     RT::LoadConfig();
420   }
421
422   # A hook supporting strange legacy ways people have added stuff on
423
424   my @addl_comp_root = ();
425   my $addl_comp_root_file = '%%%FREESIDE_CONF%%%/addl_comp_root.pl';
426   if ( -e $addl_comp_root_file ) {
427     warn "reading $addl_comp_root_file\n";
428     my $text = slurp( $addl_comp_root_file );
429     my @addl = eval $text;
430     if ( @addl && ! $@ ) {
431       @addl_comp_root = @addl;
432     } elsif ($@) {
433       warn "error parsing $addl_comp_root_file: $@\n";
434     }
435   }
436
437   my %interp = (
438     request_class        => $request_class,
439     data_dir             => '%%%MASONDATA%%%',
440     error_mode           => 'output',
441     error_format         => 'html',
442     ignore_warnings_expr => '.',
443     comp_root            => [
444                               [ 'freeside'=>'%%%FREESIDE_DOCUMENT_ROOT%%%'    ],
445                               [ 'rt'      =>'%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ],
446                               @addl_comp_root,
447                             ],
448   );
449
450   $interp{out_method} = $opt{outbuf} if $mode eq 'standalone' && $opt{outbuf};
451
452   my $fs_interp = new HTML::Mason::Interp (
453     %interp,
454     escape_flags => { 'js_string' => sub {
455                         #${$_[0]} =~ s/(['\\\n])/'\\'.($1 eq "\n" ? 'n' : $1)/ge;
456                         ${$_[0]} =~ s/(['\\])/\\$1/g;
457                         ${$_[0]} =~ s/\n/\\n/g;
458                         ${$_[0]} = "'". ${$_[0]}. "'";
459                       }
460                     },
461     compiler     => HTML::Mason::Compiler::ToObject->new(
462                       allow_globals        => [qw(%session)],
463                     ),
464   );
465
466   my $rt_interp = new HTML::Mason::Interp (
467     %interp,
468     escape_flags => { 'h' => \&RT::Interface::Web::EscapeUTF8 },
469     compiler     => HTML::Mason::Compiler::ToObject->new(
470                       default_escape_flags => 'h',
471                       allow_globals        => [qw(%session)],
472                     ),
473   );
474
475   ( $fs_interp, $rt_interp );
476
477 }
478
479 =back
480
481 =head1 BUGS
482
483 Lurking in the darkness...
484
485 =head1 SEE ALSO
486
487 L<HTML::Mason>, L<FS>, L<RT>
488
489 =cut
490
491 1;