package NGCP::Panel::Utils::InvoiceTemplate; use Sipwise::Base; use File::Temp; use XML::XPath; use IPC::System::Simple qw/capturex/; use Template; sub svg_pdf { my ($c,$svg_ref,$pdf_ref) = @_; my $svg = $$svg_ref; my $dir = File::Temp->newdir(undef, CLEANUP => 1); my $tempdir = $dir->dirname; my ($pagenum,@pagefiles) = (1); foreach my $page ( @{preprocess_svg_pdf($c,$svg_ref)}){ my $fh; my $pagefile = "$tempdir/$pagenum.svg"; push @pagefiles, $pagefile; open($fh, ">", $pagefile); binmode($fh, ":encoding(UTF-8)"); print $fh $page; close $fh; $pagenum++; } nsvg_pdf($c,25,\@pagefiles,$tempdir,$pdf_ref); return 1; } sub nsvg_pdf { my ($c,$pagesnum,$pagefiles,$tempdir,$pdf_ref) = @_; my $join_necessary = @$pagefiles > $pagesnum; my $pdf_file_number = 0; while (my @next_pages = splice @$pagefiles, 0, $pagesnum) { # For whatever reason, the pdf looks ok with zoom of 1.0 when # generated via rsvg-convert, but the print result is too big, # so we need to scale it down by 0.8 to get a mediabox of 595,842 # when using 90dpi. # (it doesn't happen with inkscape, no idea what rsvg does) #-z 0.8 , --dpi-x 72 --dpi-y 72, -w 595 -h 842/ my @cmd_args = (qw/-a -f pdf/, @next_pages); my $cmd = 'rsvg-convert'; my $cmd_full = $cmd.' '.join(' ', @cmd_args); $c and $c->log->debug( $cmd_full ); print $cmd_full.";\n"; $pdf_file_number ++; `$cmd_full > $tempdir/$pdf_file_number.pdf`; } my @cmd_args = ('-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-dPDFSETTINGS=/prepress','-sPAPERSIZE=a4', '-dFIXEDMEDIA', '-dPDFFitPage', '-sOutputFile=-', map { $tempdir.'/'.$_.'.pdf'} ( 1..$pdf_file_number )); my $cmd = 'gs'; my $cmd_full = $cmd.' '.join(' ', @cmd_args); $c and $c->log->debug( $cmd_full ); print $cmd_full.";\n"; $$pdf_ref = capturex([0], $cmd, @cmd_args); return 1; } sub preprocess_svg_pdf { my ($c,$svg_ref) = @_; my $svg = $$svg_ref; my (@pages_content); # file consists of multiple svg tags (invalid!), split them up: my(@pages) = $svg=~/())/sig; foreach my $page(@pages) { my $xp = XML::XPath->new($page); my $server_process_spec = ''; if( my $spec_nodes = $xp->find('//@server-process-units') ){ $server_process_spec = $spec_nodes->string_value(); } my $g = $xp->find('//g[contains(@class,"firsty-") and contains(@class,"lasty")]'); foreach my $node($g->get_nodelist) { my $class = $node->getAttribute('class'); my $firsty = $class; my $lasty = $class; $firsty =~ s/^.+firsty\-(\d+).*$/$1/; $lasty =~ s/^.+lasty\-(\d+).*$/$1/; if(length($firsty) && length($lasty)) { process_child_nodes($node, $firsty, $lasty, $server_process_spec); } } $page = ($xp->findnodes('/'))[0]->toString(); push @pages_content, $page; } return \@pages_content; } sub preprocess_svg { my($svg_ref) = @_; $$svg_ref=~s/(?:{\s*)?(?:\s*})?//gs; $$svg_ref = ''.$$svg_ref.''; my $xp = XML::XPath->new($$svg_ref); my $g = $xp->find('//g[@class="page"]'); foreach my $node($g->get_nodelist) { if($node->getAttribute('display')) { $node->removeAttribute('display'); } } #we can't process images on server side due to possible access restrictions $$svg_ref = ($xp->findnodes('/'))[0]->toString(); $$svg_ref =~s/^|<\/root>$//; #$$svg_ref=~s/<(g .*?)(?:display\s*=\s*["']*none["'[:blank:]]+)(.*?id *=["' ]+page[^"' ]*["' ]+)([^>]*)>/<$1$2$3>/gs; #$$svg_ref=~s/<(g .*?)(id *=["' ]+page[^"' ]*["' ]+.*?)(?:display\s*=\s*["']*none["'[:blank:]]+)([^>]*)>/<$1$2$3>/gs; } sub sanitize_svg { my ($svg_ref) = @_; my $xp = XML::XPath->new($$svg_ref); my $s = $xp->find('//script'); foreach my $node($s->get_nodelist) { if($node->getAttribute('display')) { $node->getParentNode->removeChild($node); } } #we do here nothing against TemplateToolkit code invasion - is it correct? $$svg_ref = ($xp->findnodes('/'))[0]->toString(); $$svg_ref =~ s/class="page layer"/class="page"/gi; return 1; } sub get_tt { #using of the common configuration was the reason for View::SVG my $tt = Template->new({ ENCODING => 'UTF-8', RELATIVE => 1, INCLUDE_PATH => './share/templates:/usr/share/ngcp-panel/templates', }); $tt->context->define_vmethod( hash => get_column => sub { my($item,$col) = @_; if('HASH' eq ref $item){ return $item->{$col}; } }, ); $tt->context->define_vmethod( 'scalar' => power => sub { my($value,$power) = @_; return $value ** $power; }, ); return $tt; } sub svg_content{ my ($c, $category, $content) = @_; unless ($content) { #default is the same for all - I would like to move it as something constant to itils my $default = 'invoice/default/' . $category . '_invoice_template_svg.tt'; my $t = NGCP::Panel::Utils::InvoiceTemplate::get_tt(); try { $content = $t->context->insert($default); } catch($e) { # TODO: handle error! my $msg = "failed to load default $category invoice template: $e"; $c and $c->log->error($msg); die($msg); return; } } # some part of the chain does not like content being encoded as utf8 at # that point already; decode here, and umlauts etc will be fine throughout # the chain. # TODO: does not work when loaded from db? use utf8; utf8::decode($content); return $content; } sub process_child_nodes { my ($node, $firsty, $y, $server_process_spec) = @_; for my $attr (qw/y y1 y2/) { my $a = $node->getAttribute($attr); if($a) { $a =~ s/^(\d+)\w*$/$1/; my $delta = $a - $firsty; my $newy = $y + $delta; $node->removeAttribute($attr); $node->appendAttribute(XML::XPath::Node::Attribute->new($attr, $newy.(($server_process_spec ne 'none')?'mm':'') )); } } my @children = $node->getChildNodes(); foreach my $node(@children) { process_child_nodes($node, $firsty, $y, $server_process_spec); } } sub get_dummy_data { my $data = { rescontact => { gender => 'male', firstname => 'Resellerfirst', lastname => 'Resellerlast', comregnum => 'COMREG1234567890', company => 'Resellercompany Inc.', street => 'Resellerstreet 12/3', postcode => '12345', city => 'Resellercity', country => 'Resellercountry', phonenumber => '+1234567890', mobilenumber => '+2234567890', faxnumber => '+3234567890', iban => 'RESIBAN1234567890', bic => 'RESBIC1234567890', bankname => 'Resellerbank', vatnum => 'RESVAT1234567890', gpp0 => 'RESGPP0', gpp1 => 'RESGPP1', gpp2 => 'RESGPP2', gpp3 => 'RESGPP3', gpp4 => 'RESGPP4', gpp5 => 'RESGPP5', gpp6 => 'RESGPP6', gpp7 => 'RESGPP7', gpp8 => 'RESGPP8', gpp9 => 'RESGPP9', }, customer => { id => int(rand(10000))+10000, external_id => 'Resext1234567890', vat_rate => 20.0, add_vat => 0, }, custcontact => { gender => 'male', firstname => 'Customerfirst', lastname => 'Customerlast', comregnum => 'COMREG1234567890', company => 'Customercompany Inc.', street => 'Customerstreet 12/3', postcode => '12345', city => 'Customercity', country => 'Customercountry', phonenumber => '+4234567890', mobilenumber => '+5234567890', faxnumber => '+6234567890', iban => 'CUSTIBAN1234567890', bic => 'CUSTBIC1234567890', vatnum => 'CUSTVAT1234567890', bankname => 'Customerbank', gpp0 => 'CUSTGPP0', gpp1 => 'CUSTGPP1', gpp2 => 'CUSTGPP2', gpp3 => 'CUSTGPP3', gpp4 => 'CUSTGPP4', gpp5 => 'CUSTGPP5', gpp6 => 'CUSTGPP6', gpp7 => 'CUSTGPP7', gpp8 => 'CUSTGPP8', gpp9 => 'CUSTGPP9', }, billprof => { handle => 'BILPROF12345', name => 'Test Billing Profile', prepaid => 0, interval_charge => 29.90, interval_free_time => 2000, interval_free_cash => 0, interval_unit => 'month', interval_count => 1, currency => 'EUR', }, invoice => { period_start => NGCP::Panel::Utils::DateTime::current_local()->truncate(to => 'month'), period_end => NGCP::Panel::Utils::DateTime::current_local()->truncate(to => 'month')->add(months => 1)->subtract(seconds => 1), serial => '1234567', amount_net => 12345, amount_vat => 12345*0.2, amount_total => 12345+(12345*0.2), }, calls => [ map {{ source_user => 'user', source_domain => 'example.org', source_cli => '1234567890', destination_user_in => "1".$_."1234567890", start_time => time, source_customer_cost => int( (rand()-1/2) * rand(1000000)), duration => int(rand(7200)) + 10, call_type => (qw/cfu cfb cft cfna cfs cfr cfo/)[int(rand 4)], zone => "Zone $_", zone_detail => "Detail $_", }}(1 .. 50) ], zones => { totalcost => int(rand(10000))+10000, data => [ map {{ number => int(rand(200)), customercost => int(rand(100000)), duration => int(rand(10000)), free_time => 0, zone => "Zone $_", zone_detail => "Detail $_", }}(1 .. 5) ], }, }; return $data; } 1; # vim: set tabstop=4 expandtab: