summaryrefslogtreecommitdiff
path: root/LedgerSMB.pm
blob: a7a34db6e2168f0a02fd1a60064b448d9b6e289e (plain)
  1. =head1 NAME
  2. LedgerSMB The Base class for many LedgerSMB objects, including DBObject.
  3. =head1 SYOPSIS
  4. This module creates a basic request handler with utility functions available
  5. in database objects (LedgerSMB::DBObject)
  6. =head1 METHODS
  7. =item new ()
  8. This method creates a new base request instance.
  9. =item debug (file => $path);
  10. This dumps the current object to the file if that is defined and otherwise to
  11. standard output.
  12. =item escape (string => $string);
  13. This function returns the current string escaped using %hexhex notation.
  14. =item unescape (string => $string);
  15. This function returns the $string encoded using %hexhex using ordinary notation.
  16. =item format_amount (user => $LedgerSMB::User::hash, amount => $string, precision => $integer, neg_format => (-|DRCR));
  17. The function takes a monetary amount and formats it according to the user
  18. preferences, the negative format (- or DR/CR).
  19. =item format_fields (fields => \@array);
  20. This function converts fields to their appropriate representation in
  21. HTML/SGML/XML or LaTeX.
  22. =item is_blank (msg=> $string, name => $string)
  23. This function invokes self->error($msg) if the property contains no
  24. non-whitespace characters.
  25. =item num_text_rows (string => $string, cols => $number, max => $number);
  26. This function determines the likely number of rows needed to hold text in a
  27. textbox. It returns either that number or max, which ever is lower.
  28. =item redirect (msg => $string)
  29. This function redirects to the script and argument set determined by
  30. $self->{callback}, and if this is not set, goes to an info screen and prints
  31. $msg.
  32. =head1 Copyright (C) 2006, The LedgerSMB core team.
  33. # This work contains copyrighted information from a number of sources all used
  34. # with permission.
  35. #
  36. # This file contains source code included with or based on SQL-Ledger which
  37. # is Copyright Dieter Simader and DWS Systems Inc. 2000-2005 and licensed
  38. # under the GNU General Public License version 2 or, at your option, any later
  39. # version. For a full list including contact information of contributors,
  40. # maintainers, and copyright holders, see the CONTRIBUTORS file.
  41. #
  42. # Original Copyright Notice from SQL-Ledger 2.6.17 (before the fork):
  43. # Copyright (C) 2000
  44. #
  45. # Author: DWS Systems Inc.
  46. # Web: http://www.sql-ledger.org
  47. #
  48. # Contributors: Thomas Bayen <bayen@gmx.de>
  49. # Antti Kaihola <akaihola@siba.fi>
  50. # Moritz Bunkus (tex)
  51. # Jim Rawlings <jim@your-dba.com> (DB2)
  52. #======================================================================
  53. =cut
  54. use Math::BigFloat lib=>'GMP';
  55. use LedgerSMB::Sysconfig;
  56. use Data::Dumper;
  57. use strict;
  58. package LedgerSMB;
  59. sub new {
  60. # This will probably be the last to be revised.
  61. my $type = shift;
  62. my $argstr = shift;
  63. read(STDIN, $_, $ENV{CONTENT_LENGTH});
  64. if ($argstr){
  65. $_ = $argstr;
  66. }
  67. elsif ($ENV{QUERY_STRING}) {
  68. $_ = $ENV{QUERY_STRING};
  69. }
  70. elsif ($ARGV[0]) {
  71. $_ = $ARGV[0];
  72. }
  73. my $self = {};
  74. %$self = split /[&=]/;
  75. for (keys %$self) { $self->{$_} = unescape("", $self->{$_}) }
  76. if (substr($self->{action}, 0, 1) !~ /( |\.)/) {
  77. $self->{action} = lc $self->{action};
  78. $self->{action} =~ s/( |-|,|\#|\/|\.$)/_/g;
  79. }
  80. $self->{menubar} = 1 if $self->{path} =~ /lynx/i;
  81. #menubar will be deprecated, replaced with below
  82. $self->{lynx} = 1 if $self->{path} =~ /lynx/i;
  83. $self->{version} = "1.3.0 Alpha 0 Pre";
  84. $self->{dbversion} = "1.2.0";
  85. bless $self, $type;
  86. }
  87. sub debug {
  88. my $self = shift @_;
  89. my %args = @_;
  90. my $file = $args{file};
  91. my $d = Data::Dumper->new([@_]);
  92. $d->Sortkeys(1);
  93. if ($file) {
  94. open(FH, '>', "$file") or die $!;
  95. print FH $d->Dump();
  96. close(FH);
  97. } else {
  98. print "\n";
  99. print $d->Dump();
  100. }
  101. }
  102. sub escape {
  103. my ($self) = @_;
  104. my %args = @_;
  105. my $str = $args{string};
  106. my $regex = qr/([^a-zA-Z0-9_.-])/;
  107. $str =~ s/$regex/sprintf("%%%02x", ord($1))/ge;
  108. $str;
  109. }
  110. sub unescape {
  111. my ($self) = @_;
  112. my %args = @_;
  113. my $str = $args{string};
  114. $str =~ tr/+/ /;
  115. $str =~ s/\\$//;
  116. $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
  117. $str =~ s/\r?\n/\n/g;
  118. $str;
  119. }
  120. sub is_blank {
  121. my $self = shift @_;
  122. my %args = @_;
  123. my $name = $args{name};
  124. my $msg = $args{msg};
  125. $self->error($msg) if $self->{$name} =~ /^\s*$/;
  126. }
  127. sub num_text_rows {
  128. my $self = shift @_;
  129. my %args = @_;
  130. my $string = $args{string};
  131. my $cols = $args{cols};
  132. my $maxrows = $args{max};
  133. my $rows = 0;
  134. for (split /\n/, $string) {
  135. my $line = $_;
  136. while (length($line) > $cols){
  137. my $fragment = substr($line, 0, $cols + 1);
  138. my $fragment = s/^(.*)\S*$/$1/;
  139. $line = s/$fragment//;
  140. if ($line eq $fragment){ # No word breaks!
  141. $line = "";
  142. }
  143. ++$rows;
  144. }
  145. ++$rows;
  146. }
  147. $maxrows = $rows unless defined $maxrows;
  148. return ($rows > $maxrows) ? $maxrows : $rows;
  149. }
  150. sub redirect {
  151. my $self = shift @_;
  152. my %args = @_;
  153. my $msg = $args{msg};
  154. if ($self->{callback} || !$msg) {
  155. main::redirect();
  156. } else {
  157. $self->info($msg);
  158. }
  159. }
  160. sub format_fields {
  161. # We should look at moving this into LedgerSMB::Template.
  162. # And cleaning it up...... Chris
  163. my $self = shift @_;
  164. my %args = @_;
  165. my @fields = @{$args{fields}};
  166. my $format = $self->{format};
  167. if ($self->{format} =~ /(postscript|pdf)/) {
  168. $format = 'tex';
  169. }
  170. my %replace = (
  171. 'order' => {
  172. html => [ '<', '>', '\n', '\r' ],
  173. txt => [ '\n', '\r' ],
  174. tex => [ quotemeta('\\'), '&', '\n','\r',
  175. '\$', '%', '_', '#',
  176. quotemeta('^'), '{', '}', '<', '>', '£'
  177. ] },
  178. html => { '<' => '&lt;', '>' => '&gt;','\n' => '<br />',
  179. '\r' => '<br />' },
  180. txt => { '\n' => "\n", '\r' => "\r" },
  181. tex => {'&' => '\&', '$' => '\$', '%' => '\%', '_' => '\_',
  182. '#' => '\#', quotemeta('^') => '\^\\', '{' => '\{',
  183. '}' => '\}', '<' => '$<$', '>' => '$>$',
  184. '\n' => '\newline ', '\r' => '\newline ',
  185. '£' => '\pounds ', quotemeta('\\') => '/'}
  186. );
  187. my $key;
  188. foreach $key (@{ $replace{order}{$format} }) {
  189. for (@fields) { $self->{$_} =~ s/$key/$replace{$format}{$key}/g }
  190. }
  191. }
  192. # TODO: Either we should have an amount class with formats and such attached
  193. # Or maybe we should move this into the user class...
  194. sub format_amount {
  195. my $self = shift @_;
  196. my %args = @_;
  197. my $myconfig = $args{user};
  198. my $amount = $args{amount};
  199. my $places = $args{precision};
  200. my $dash = $args{neg_format};
  201. my $negative ;
  202. if ($amount){
  203. $amount = $self->parse_amount($myconfig, $amount);
  204. $negative = ($amount < 0);
  205. $amount =~ s/-//;
  206. }
  207. if ($places =~ /\d+/) {
  208. #$places = 4 if $places == 2;
  209. $amount = $self->round_amount($amount, $places);
  210. }
  211. # is the amount negative
  212. # Parse $myconfig->{numberformat}
  213. my ($ts, $ds) = ($1, $2);
  214. if ($amount) {
  215. if ($myconfig->{numberformat}) {
  216. my ($whole, $dec) = split /\./, "$amount";
  217. $amount = join '', reverse split //, $whole;
  218. if ($places) {
  219. $dec .= "0" x $places;
  220. $dec = substr($dec, 0, $places);
  221. }
  222. if ($myconfig->{numberformat} eq '1,000.00') {
  223. $amount =~ s/\d{3,}?/$&,/g;
  224. $amount =~ s/,$//;
  225. $amount = join '', reverse split //, $amount;
  226. $amount .= "\.$dec" if ($dec ne "");
  227. }
  228. if ($myconfig->{numberformat} eq '1 000.00') {
  229. $amount =~ s/\d{3,}?/$& /g;
  230. $amount =~ s/\s$//;
  231. $amount = join '', reverse split //, $amount;
  232. $amount .= "\.$dec" if ($dec ne "");
  233. }
  234. if ($myconfig->{numberformat} eq "1'000.00") {
  235. $amount =~ s/\d{3,}?/$&'/g;
  236. $amount =~ s/'$//;
  237. $amount = join '', reverse split //, $amount;
  238. $amount .= "\.$dec" if ($dec ne "");
  239. }
  240. if ($myconfig->{numberformat} eq '1.000,00') {
  241. $amount =~ s/\d{3,}?/$&./g;
  242. $amount =~ s/\.$//;
  243. $amount = join '', reverse split //, $amount;
  244. $amount .= ",$dec" if ($dec ne "");
  245. }
  246. if ($myconfig->{numberformat} eq '1000,00') {
  247. $amount = "$whole";
  248. $amount .= ",$dec" if ($dec ne "");
  249. }
  250. if ($myconfig->{numberformat} eq '1000.00') {
  251. $amount = "$whole";
  252. $amount .= ".$dec" if ($dec ne "");
  253. }
  254. if ($dash =~ /-/) {
  255. $amount = ($negative) ? "($amount)" : "$amount";
  256. } elsif ($dash =~ /DRCR/) {
  257. $amount = ($negative) ? "$amount DR" : "$amount CR";
  258. } else {
  259. $amount = ($negative) ? "-$amount" : "$amount";
  260. }
  261. }
  262. } else {
  263. if ($dash eq "0" && $places) {
  264. if ($myconfig->{numberformat} eq '1.000,00') {
  265. $amount = "0".","."0" x $places;
  266. } else {
  267. $amount = "0"."."."0" x $places;
  268. }
  269. } else {
  270. $amount = ($dash ne "") ? "$dash" : "";
  271. }
  272. }
  273. $amount;
  274. }
  275. sub parse_amount {
  276. my ($self, $myconfig, $amount) = @_;
  277. if ($amount eq '' or $amount == undef){
  278. return 0;
  279. }
  280. if (UNIVERSAL::isa($amount, 'Math::BigFloat')){ # Amount may not be an object
  281. return $amount;
  282. }
  283. my $numberformat = $myconfig->{numberformat};
  284. if (($numberformat eq '1.000,00') ||
  285. ($numberformat eq '1000,00')) {
  286. $amount =~ s/\.//g;
  287. $amount =~ s/,/./;
  288. }
  289. if ($numberformat eq '1 000.00'){
  290. $amount =~ s/\s//g;
  291. }
  292. if ($numberformat eq "1'000.00") {
  293. $amount =~ s/'//g;
  294. }
  295. $amount =~ s/,//g;
  296. if ($amount =~ s/\((\d*\.?\d*)\)/$1/){
  297. $amount = $1 * -1;
  298. }
  299. if ($amount =~ s/(\d*\.?\d*)\s?DR/$1/){
  300. $amount = $1 * -1;
  301. }
  302. $amount =~ s/\s?CR//;
  303. $amount = new Math::BigFloat($amount);
  304. return ($amount * 1);
  305. }
  306. sub round_amount {
  307. my ($self, $amount, $places) = @_;
  308. # These rounding rules follow from the previous implementation.
  309. # They should be changed to allow different rules for different accounts.
  310. Math::BigFloat->round_mode('+inf') if $amount >= 0;
  311. Math::BigFloat->round_mode('-inf') if $amount < 0;
  312. $amount = Math::BigFloat->new($amount)->ffround(-$places) if $places >= 0;
  313. $amount = Math::BigFloat->new($amount)->ffround(-($places-1)) if $places < 0;
  314. return $amount;
  315. }
  316. sub call_procedure {
  317. my $self = shift @_;
  318. my %args = @_;
  319. my $procname = $args{procname};
  320. my @args = @{$args{args}};
  321. my $argstr = "";
  322. my @results;
  323. for (1 .. scalar @args){
  324. $argstr .= "?, ";
  325. }
  326. $argstr =~ s/\, $//;
  327. my $query = "SELECT * FROM $procname()";
  328. $query =~ s/\(\)/($argstr)/;
  329. my $sth = $self->{dbh}->prepare($query);
  330. $sth->execute(@args);
  331. while (my $ref = $sth->fetchrow_hashref('NAME_lc')){
  332. push @results, $ref;
  333. }
  334. @results;
  335. }
  336. sub datetonum {
  337. my ($self, $myconfig, $date, $picture) = @_;
  338. my ($yy, $mm, $dd);
  339. if ($date && $date =~ /\D/) {
  340. if ($myconfig->{dateformat} =~ /^yy/) {
  341. ($yy, $mm, $dd) = split /\D/, $date;
  342. }
  343. if ($myconfig->{dateformat} =~ /^mm/) {
  344. ($mm, $dd, $yy) = split /\D/, $date;
  345. }
  346. if ($myconfig->{dateformat} =~ /^dd/) {
  347. ($dd, $mm, $yy) = split /\D/, $date;
  348. }
  349. $dd *= 1;
  350. $mm *= 1;
  351. $yy += 2000 if length $yy == 2;
  352. $dd = substr("0$dd", -2);
  353. $mm = substr("0$mm", -2);
  354. $date = "$yy$mm$dd";
  355. }
  356. $date;
  357. }
  358. # Database routines used throughout
  359. sub db_init {
  360. my ($self, $myconfig) = @_;
  361. $self->{dbh} = $self->dbconnect_noauto($myconfig) || $self->dberror();
  362. my $query =
  363. "SELECT t.extends,
  364. coalesce (t.table_name, 'custom_' || extends)
  365. || ':' || f.field_name as field_def
  366. FROM custom_table_catalog t
  367. JOIN custom_field_catalog f USING (table_id)";
  368. my $sth = $self->{dbh}->prepare($query);
  369. $sth->execute;
  370. my $ref;
  371. while ($ref = $sth->fetchrow_hashref('NAME_lc')){
  372. push @{$self->{custom_db_fields}{$ref->{extends}}},
  373. $ref->{field_def};
  374. }
  375. }
  376. sub dbconnect_noauto {
  377. my ($self, $myconfig) = @_;
  378. # connect to database
  379. my $dbh = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, {AutoCommit => 0}) or $self->dberror;
  380. # set db options
  381. if ($myconfig->{dboptions}) {
  382. $dbh->do($myconfig->{dboptions});
  383. }
  384. $dbh;
  385. }
  386. sub redo_rows {
  387. my ($self, $flds, $new, $count, $numrows) = @_;
  388. my @ndx = ();
  389. for (1 .. $count) {
  390. push @ndx, { num => $new->[$_-1]->{runningnumber}, ndx => $_ }
  391. }
  392. my $i = 0;
  393. my $j;
  394. # fill rows
  395. foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
  396. $i++;
  397. $j = $item->{ndx} - 1;
  398. for (@{$flds}) { $self->{"${_}_$i"} = $new->[$j]->{$_} }
  399. }
  400. # delete empty rows
  401. for $i ($count + 1 .. $numrows) {
  402. for (@{$flds}) { delete $self->{"${_}_$i"} }
  403. }
  404. }
  405. sub merge {
  406. my ($self, $src) = @_;
  407. for my $arg ($self, $src){
  408. shift;
  409. }
  410. my @keys;
  411. if (scalar @keys){
  412. @keys = @_;
  413. print "Keys: ". scalar @keys . "\n";
  414. }
  415. else {
  416. @keys = keys %{$src};
  417. print "Keys: ". scalar @keys . "\n";
  418. }
  419. for my $arg (keys %$src){
  420. $self->{$arg} = $src->{$arg};
  421. }
  422. }
  423. 1;