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