webgui/lib/WebGUI/Asset/Wobject/SQLReport.pm
2008-10-14 00:06:08 +00:00

768 lines
25 KiB
Perl

package WebGUI::Asset::Wobject::SQLReport;
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2008 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use strict;
use WebGUI::DatabaseLink;
use WebGUI::International;
use WebGUI::Macro;
use WebGUI::Paginator;
use WebGUI::SQL;
use WebGUI::Utility;
use WebGUI::Asset::Wobject;
use WebGUI::Cache;
use WebGUI::Text qw(:csv);
our @ISA = qw(WebGUI::Asset::Wobject);
#-------------------------------------------------------------------
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my $i18n = WebGUI::International->new($session,"Asset_SQLReport");
push(@{$definition}, {
assetName=>$i18n->get('assetName'),
uiLevel => 5,
icon=>'sqlReport.gif',
tableName=>'SQLReport',
className=>'WebGUI::Asset::Wobject::SQLReport',
properties=>{
templateId =>{
fieldType=>"template",
defaultValue=>'PBtmpl0000000000000059'
},
cacheTimeout=>{
fieldType=>"interval",
defaultValue=>0
},
paginateAfter=>{
fieldType=>"integer",
defaultValue=>50
},
dbQuery1=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef,
},
prequeryStatements1=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
preprocessMacros1=>{
fieldType=>"yesNo",
defaultValue=>0
},
placeholderParams1=>{
fieldType=>"textarea",
defaultValue=>undef
},
databaseLinkId1=>{
fieldType=>"databaseLink",
defaultValue=>0
},
dbQuery2=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
prequeryStatements2=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
preprocessMacros2=>{
fieldType=>"yesNo",
defaultValue=>0
},
placeholderParams2=>{
fieldType=>"textarea",
defaultValue=>undef
},
databaseLinkId2=>{
fieldType=>"databaseLink",
defaultValue=>0
},
dbQuery3=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
prequeryStatements3=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
preprocessMacros3=>{
fieldType=>"yesNo",
defaultValue=>0
},
placeholderParams3=>{
fieldType=>"textarea",
defaultValue=>undef
},
databaseLinkId3=>{
fieldType=>"databaseLink",
defaultValue=>0
},
dbQuery4=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
prequeryStatements4=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
preprocessMacros4=>{
fieldType=>"yesNo",
defaultValue=>0
},
placeholderParams4=>{
fieldType=>"textarea",
defaultValue=>undef
},
databaseLinkId4=>{
fieldType=>"databaseLink",
defaultValue=>0
},
dbQuery5=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
prequeryStatements5=>{
fieldType=>"codearea",
syntax => "sql",
defaultValue=>undef
},
preprocessMacros5=>{
fieldType=>"yesNo",
defaultValue=>0
},
placeholderParams5=>{
fieldType=>"textarea",
defaultValue=>undef
},
databaseLinkId5=>{
fieldType=>"databaseLink",
defaultValue=>0
},
debugMode=>{
fieldType=>"yesNo",
defaultValue=>0
},
# download
downloadType=>{
fieldType=>"text",
defaultValue=>"none",
},
downloadFilename=>{
fieldType=>"text",
defaultValue=>"",
},
downloadTemplateId=>{
fieldType=>"template",
defaultValue=>'SQLReportDownload00001',
},
downloadMimeType=>{
fieldType=>"text",
defaultValue=>"text/html",
},
downloadUserGroup=>{
fieldType=>"group",
defaultValue=>"text/html",
},
}
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
sub getEditForm {
my $self = shift;
my $tabform = $self->SUPER::getEditForm();
my $i18n = WebGUI::International->new($self->session,"Asset_SQLReport");
$tabform->getTab("display")->template(
-value=>$self->getValue('templateId'),
-label=>$i18n->get(72),
-hoverHelp=>$i18n->get('72 description'),
-namespace=>"SQLReport"
);
$tabform->getTab("properties")->yesNo(
-name=>"debugMode",
-label=>$i18n->get(16),
-hoverHelp=>$i18n->get('16 description'),
-value=>$self->getValue("debugMode")
);
$tabform->getTab("display")->interval(
-name=>"cacheTimeout",
-label=>$i18n->get('cache timeout'),
-hoverHelp=>$i18n->get('cache timeout description'),
-uiLevel => 8,
-value=>$self->getValue("cacheTimeout")
);
### Download
# Download Type
my %downloadTypes;
tie %downloadTypes, 'Tie::IxHash',
"none" => "No Download",
"csv" => "CSV",
"template" => "Template",
;
$tabform->getTab("properties")->radioList(
-name=>"downloadType", # ID is downloadType_formId
-label=>$i18n->get("download type"),
-hoverHelp=>$i18n->get("download type description"),
-vertical=>1,
-options=> \%downloadTypes,
-defaultValue=>"none",
-value=>$self->getValue("downloadType"),
-extras=> "onclick='changeDownloadType(this)'"
);
# Download Filename
$tabform->getTab("properties")->text(
-name=>"downloadFilename", # ID is downloadFilename_formId
-label=>$i18n->get("download filename"),
-hoverHelp=>$i18n->get("download filename description"),
-value=>$self->getValue("downloadFilename"),
);
# Download template (if necessary)
$tabform->getTab("properties")->template(
-name=>"downloadTemplateId", # ID is downloadTemplateId_formId
-label=>$i18n->get("download template"),
-hoverHelp=>$i18n->get("download template description"),
-value=>$self->getValue("downloadTemplateId"),
-namespace=>"SQLReport/Download",
);
# Download mimeType (if necessary)
my %downloadMimeType;
tie %downloadMimeType, 'Tie::IxHash',
"application/octet-stream" => "application/octet-stream",
"application/xml" => "application/xml",
"application/csv" => "application/csv",
"text/html" => "text/html",
"text/plain" => "text/plain",
;
$tabform->getTab("properties")->selectBox(
-name=>"downloadMimeType",
-label=>$i18n->get("download mimetype"),
-hoverHelp=>$i18n->get("download mimetype description"),
-options=> \%downloadMimeType,
-value=>$self->getValue("downloadMimeType"),
-defaultValue=>"application/octet-stream",
);
# Download UserGroup
$tabform->getTab("security")->group(
-name=>"downloadUserGroup",
-label=>$i18n->get("download usergroup"),
-hoverHelp=>$i18n->get("download usergroup description"),
-value=>$self->getValue("downloadUserGroup"),
-defaultValue=>$self->getValue("groupIdView"),
);
# javascript
$self->session->style->setScript("/extras/wobject/SQLReport/editFormDownload.js");
### /DOWNLOAD
# Add toggleQuery javascript
$tabform->getTab("properties")->raw(qq|
<script type="text/javascript">
function toggleQuery(Id) {
queryClass = "query" + Id;
var tr = document.getElementsByTagName("tr");
if (tr == null) return undef;
for (i=0; i < tr.length; i++) {
if(tr[i].className == queryClass) {
if(tr[i].style.display == 'none') {
tr[i].style.display = '';
} else {
tr[i].style.display = 'none';
}
}
}
}
</script>
|);
for my $nr (1..5) {
# Set TR class for this query properties
$tabform->getTab("properties")->trClass("query".$nr);
$tabform->getTab("properties")->readOnly(
-value=>"<hr>",
-label=>join '', "<b>", $i18n->get('4'), $nr,":</b>",
);
$tabform->getTab("properties")->yesNo(
-name=>"preprocessMacros".$nr,
-label=>$i18n->get(15),
-hoverHelp=>$i18n->get('15 description'),
-value=>$self->getValue("preprocessMacros".$nr)
);
$tabform->getTab("properties")->textarea(
-name=>"placeholderParams".$nr,
-label=>$i18n->get('Placeholder Parameters'),
-hoverHelp=>$i18n->get('Placeholder Parameters description'),
-value=>$self->getValue("placeholderParams".$nr)
);
$tabform->getTab("properties")->codearea(
-name => "prequeryStatements".$nr,
-label => $i18n->get('Prequery statements'),
-hoverHelp => $i18n->get('Prequery statements description'),
-syntax => "sql",
-value => $self->getValue("prequeryStatements".$nr),
);
$tabform->getTab("properties")->codearea(
-name=>"dbQuery".$nr,
-label=>$i18n->get(4),
-hoverHelp=>$i18n->get('4 description'),
-syntax => "sql",
-value=>$self->getValue("dbQuery".$nr)
);
$tabform->getTab("properties")->databaseLink(
-name=>"databaseLinkId".$nr,
-value=>$self->getValue("databaseLinkId".$nr)
);
# Add a "Add another query" button
if ($nr < 5 and ($self->get("dbQuery".($nr+1)) eq "" || ($self->get("dbQuery".($nr)) eq "" and $self->get("dbQuery".($nr+1)) ne ""))) {
$tabform->getTab("properties")->button(
-value=>$i18n->get('Add another query'),
-extras=>'onclick="toggleQuery(\''.($nr+1).'\'); this.style.display=\'none\';"',
-noWait=>1
);
}
# Make empty query blocks invisible
if ($nr > 1 && ($self->get("dbQuery".$nr) eq "" || $self->get("dbQuery".($nr-1)) eq "")) {
$tabform->getTab("properties")->raw(qq|
<script type="text/javascript">
toggleQuery('$nr');
</script>
|);
}
}
# Undefine TR class
$tabform->getTab("properties")->trClass();
$tabform->getTab("display")->integer(
-name=>"paginateAfter",
-label=>$i18n->get(14),
-hoverHelp=>$i18n->get('14 description'),
-value=>$self->getValue("paginateAfter")
);
return $tabform;
}
#-------------------------------------------------------------------
=head2 download ( )
Returns the SQLReport in the configured manner. Returns nothing if download is
not enabled.
=cut
sub download {
my $self = shift;
# Instead of going through some costly exercises...
return undef if ($self->getValue("downloadType") eq "none");
# Initiate an empty debug loop
$self->{_debug_loop} = [] ;
# Store queries in class
$self->_storeQueries();
# If we're downloading CSV
if ($self->getValue("downloadType") eq "csv") {
my $data = $self->_processQuery(0,0);
my $out = "";
### Loop through the returned structure and put it through Text::CSV
# Column heads
$out .= joinCSV(
map { $_->{"field.name"} }
@{$data->{rows_loop}->[0]->{"row.field_loop"}}
);
# Data lines
for my $row (@{$data->{rows_loop}}) {
$out .= "\n".joinCSV(map { $_->{"field.value"} }
@{$row->{"row.field_loop"}}
);
}
return $out;
}
elsif ($self->getValue("downloadType") eq "template") {
my $data = $self->_processQuery(1,0);
return $self->processTemplate($data,$self->get("downloadTemplateId"));
}
else {
# I don't know what to do
return undef;
}
}
#-------------------------------------------------------------------
=head2 prepareView ( )
See WebGUI::Asset::prepareView() for details.
=cut
sub prepareView {
my $self = shift;
$self->SUPER::prepareView();
my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId"));
$template->prepare($self->getMetaDataAsTemplateVariables);
$self->{_viewTemplate} = $template;
}
#-------------------------------------------------------------------
=head2 purgeCache ( )
See WebGUI::Asset::purgeCache() for details.
=cut
sub purgeCache {
my $self = shift;
WebGUI::Cache->new($self->session,"view_".$self->getId)->delete;
$self->SUPER::purgeCache;
}
#-------------------------------------------------------------------
sub view {
my $self = shift;
if (!$self->session->var->isAdminOn && $self->get("cacheTimeout") > 10) {
my $out = WebGUI::Cache->new($self->session,"view_".$self->getId)->get;
return $out if $out;
}
# Initiate an empty debug loop
$self->{_debug_loop} = [] ;
# Store queries in class
$self->_storeQueries();
# Process the quer(y|ies)
my $var = $self->_processQuery();
# Add debug loop to template vars
$var->{'debug_loop'} = $self->{_debug_loop};
#use Data::Dumper; return '<pre>'.Dumper($var).'</pre>';
# Add the "Download data" link if the user is allowed to download
if ($self->getValue("downloadType") ne "none"
&& $self->session->user->isInGroup($self->getValue("downloadUserGroup"))
) {
$var->{'canDownload'} = 1;
$var->{'downloadLink'} = $self->_getDownloadLink($self);
}
my $out = $self->processTemplate($var,undef,$self->{_viewTemplate});
if (!$self->session->var->isAdminOn && $self->get("cacheTimeout") > 10) {
WebGUI::Cache->new($self->session,"view_".$self->getId)->set($out,$self->get("cacheTimeout"));
}
return $out;
}
#-------------------------------------------------------------------
# Create download link be adding all the passed in parameters and values to the url
sub _getDownloadLink {
my $self = shift;
my %params;
for my $nr (1 .. 5) {
foreach my $row (split(/\n/,$self->{_query}{$nr}{placeholderParams})) {
$row =~ s/^\s+//;
$row =~ s/\s+$//;
if ($row =~ /^form:(.*)/) {
$params{$1} ||= $self->session->form->param($1);
}
}
}
my $url = "func=download";
for my $param (sort keys %params) {
$url .= ";$param=" . $self->session->url->escape($params{$param});
}
return $self->getUrl($url);
}
#-------------------------------------------------------------------
sub _storeQueries {
my $self = shift;
for my $nr (1..5) {
if($self->get("dbQuery".$nr)) {
$self->{_query}{$nr} = {
dbQuery => $self->get("dbQuery".$nr),
databaseLinkId => $self->get("databaseLinkId".$nr),
preprocessMacros => $self->get("preprocessMacros".$nr),
placeholderParams => $self->get("placeholderParams".$nr),
rowData => {},
};
}
}
}
#-------------------------------------------------------------------
sub _parsePlaceholderParams {
my $self = shift;
my $params = shift;
my @placeholderParams;
foreach my $row (split(/\n/,$params)) {
$row =~ s/^\s+//;
$row =~ s/\s+$//;
next if ($row eq '');
my ($type,$field) = split(/:/,$row);
my $param;
if($type =~ /^form/) {
$param = $self->session->form->process($field);
} elsif ($type =~ /^query(\d)/) {
$param = $self->{_query}{$1}{rowData}{$field};
}
else {
$param = $row;
}
WebGUI::Macro::process($self->session,\$param);
push(@placeholderParams, $param);
}
return \@placeholderParams;
}
#-------------------------------------------------------------------
# _processQuery($nest, $page, $nr)
# Recursive sub to process this SQLReport's queries.
# Arguments: $nest - If true, will run the nested queries. Defaults to true
# $page - If true, will paginate. Defaults to true
# $nr - This query number. Defaults to one. This is used
# internally to tell which query we're performing. You should not
# send this yourself.
# Returns: A reference to a datastructure containing template variables to
# be passed to processTemplate()
sub _processQuery {
my $self = shift;
my $nest = shift;
$nest = 1 unless defined $nest;
my $page = shift;
$page = 1 unless defined $page;
my $nr = shift || 1;
my ($query, %var, $prefix);
if($nr > 1) {
$prefix = 'query'.$nr.'.';
}
if (! $self->{_query}{$nr}{dbQuery}) {
$self->session->errorHandler->warn("No query specified for query $nr on '" . $self->getId . "'");
push @{$self->{_debug_loop}}, { 'debug.output' => "No query specfied for query $nr" };
return \%var;
}
# Parse placeholder parameters
my $placeholderParams = $self->_parsePlaceholderParams($self->{_query}{$nr}{placeholderParams});
# Preprocess macros
if ($self->{_query}{$nr}{preprocessMacros}) {
$query = $self->{_query}{$nr}{dbQuery};
WebGUI::Macro::process($self->session,\$query);
} else {
$query = $self->{_query}{$nr}{dbQuery};
}
my $i18n = WebGUI::International->new($self->session,"Asset_SQLReport");
push(@{$self->{_debug_loop}},{'debug.output'=>$i18n->get(17).$query});
push(@{$self->{_debug_loop}},{'debug.output'=>$i18n->get('debug placeholder parameters').join(",",@$placeholderParams)});
my $dbLink = WebGUI::DatabaseLink->new($self->session,$self->{_query}{$nr}{databaseLinkId});
if (!$dbLink) {
$self->session->errorHandler->error("Could not find database link for query #$nr: '".$self->{_query}{$nr}{databaseLinkId}."'. Has it been created?");
push @{$self->{_debug_loop}}, { 'debug.output' => 'Could not find database link'};
return \%var;
}
my $dbh = $dbLink->db;
if (defined $dbh) {
if ($dbLink->queryIsAllowed($query)) {
# Check and execute prequery statements first
foreach (split(/\n/, $self->getValue("prequeryStatements".$nr))) {
my $prequeryStatement = $_;
WebGUI::Macro::process($self->session, \$prequeryStatement) if ($self->{_query}{$nr}{preprocessMacros});
if ($dbLink->queryIsAllowed($prequeryStatement)) {
my $sth = $dbh->unconditionalRead($prequeryStatement);
if ($sth->errorCode > 0) {
push(@{$self->{_debug_loop}},{
'debug.output' => $i18n->get('Prequery error').' "'.$prequeryStatement.'": '.$sth->errorMessage
});
}
else {
push(@{$self->{_debug_loop}},{
'debug.output' => "Prequery: $prequeryStatement"
});
}
$sth->finish;
}
else {
push(@{$self->{_debug_loop}},{'debug.output'=>$i18n->get("Prequery not allowed").$prequeryStatement});
}
}
my $url = $self->getUrl('func=view');
foreach ($self->session->form->param) {
unless ($_ eq "pn" || $_ eq "op" || $_ eq "func" || $_ =~ /identifier/i || $_ =~ /password/i) {
$url = $self->session->url->append($url, $self->session->url->escape($_)
.'='.$self->session->url->escape($self->session->form->process($_)));
}
}
my $paginateAfter = ($page == 1) ? $self->get("paginateAfter") : 99999999;
my $paginatePage = ($nr > 1) ? 1 : $self->session->form->param('pn');
my $p = WebGUI::Paginator->new($self->session,$url,$paginateAfter, undef, $paginatePage);
my $error = $p->setDataByQuery($query,$dbh,1,$placeholderParams);
if ($error ne "") {
$self->session->errorHandler->warn("There was a problem with the query: ".$error);
push(@{$self->{_debug_loop}},{'debug.output'=>$i18n->get(11)." ".$error});
}
else {
my $first = 1;
my @columns;
my @rows;
my $rownum = 1;
foreach my $data (@{$p->getPageData($paginatePage)}) {
$self->{_query}{$nr}{rowData} = $data;
my %row;
my $colnum = 1;
my @fields;
foreach my $name ($p->getColumnNames) {
if ($first) {
push(@columns,{
'column.number'=>$colnum,
'column.name'=>$name
});
}
push(@fields,{
'field.number'=>$colnum,
'field.name'=>$name,
'field.value'=>$data->{$name}
});
$colnum++;
$row{$prefix.'row.field.'.$name.'.value'} = $data->{$name};
}
# Process nested query
if($nest && $self->{_query}{$nr + 1}{dbQuery}) {
my $nest = $self->_processQuery($nest,$page,$nr+1);
%row = (%row , %$nest);
$row{$prefix.'hasNest'} = $nest->{'query'.($nr+1).'.rows.count'};
}
$row{$prefix.'row.number'} = $rownum;
$row{$prefix.'row.field_loop'} = \@fields;
push(@rows,\%row);
$first = 0;
$rownum++;
}
$var{$prefix.'columns_loop'} = \@columns;
$var{$prefix.'rows_loop'} = \@rows;
$var{$prefix.'columns.count'} = scalar(@columns);
$var{$prefix.'rows.count'} = $p->getRowCount;
$var{$prefix.'rows.count.isZero'} = ($p->getRowCount < 1);
$var{$prefix.'rows.count.isZero.label'} = $i18n->get(18);
$p->appendTemplateVars(\%var) if ($nr == 1);
}
} else {
push(@{$self->{_debug_loop}},{'debug.output'=>$i18n->get(10)});
$self->session->errorHandler->warn("SQLReport [".$self->getId."] The SQL query is improperly formatted.");
}
$dbLink->disconnect;
} else {
push(@{$self->{_debug_loop}},{'debug.output'=>$i18n->get(12)});
$self->session->errorHandler->warn("SQLReport [".$self->getId."] Could not connect to database.");
}
return \%var;
}
#-------------------------------------------------------------------
=head2 www_download ( )
Calls download() to let user download the SQLReport in the configured manner.
=cut
sub www_download {
my $self = shift;
# Only allow if download type is not "none"
return undef if $self->getValue("downloadType") eq "none";
# Only allow users in appropriate group
return $self->session->privilege->noAccess()
unless $self->session->user->isInGroup($self->getValue("downloadUserGroup"));
# Set filename and mimetype
if ($self->getValue("downloadType") eq "csv")
{
$self->session->http->setFilename($self->getValue("downloadFilename"),"application/octet-stream");
}
else
{
$self->session->http->setFilename($self->getValue("downloadFilename"),$self->getValue("downloadMimeType"));
}
$self->session->http->sendHeader;
return $self->download;
}
#-------------------------------------------------------------------
=head2 www_view ( )
See WebGUI::Asset::Wobject::www_view() for details.
=cut
sub www_view {
my $self = shift;
$self->session->http->setCacheControl($self->get("cacheTimeout"));
$self->SUPER::www_view(@_);
}
1;