import Fima (by Thomas Trethan <thomas@trethan.net>)
authorChuck Hagenbuch <chuck@horde.org>
Sat, 4 Apr 2009 04:58:05 +0000 (00:58 -0400)
committerChuck Hagenbuch <chuck@horde.org>
Sat, 4 Apr 2009 04:58:05 +0000 (00:58 -0400)
120 files changed:
fima/COPYING [new file with mode: 0644]
fima/README [new file with mode: 0644]
fima/account.php [new file with mode: 0644]
fima/accounts.php [new file with mode: 0644]
fima/config/.htaccess [new file with mode: 0644]
fima/config/conf.xml [new file with mode: 0644]
fima/config/menu.php.dist [new file with mode: 0644]
fima/config/prefs.php.dist [new file with mode: 0644]
fima/config/report.php.dist [new file with mode: 0644]
fima/data.php [new file with mode: 0644]
fima/docs/CHANGES [new file with mode: 0644]
fima/docs/CREDITS [new file with mode: 0644]
fima/docs/INSTALL [new file with mode: 0644]
fima/docs/RELEASE_NOTES [new file with mode: 0644]
fima/docs/TODO [new file with mode: 0644]
fima/index.php [new file with mode: 0644]
fima/ledgers/create.php [new file with mode: 0644]
fima/ledgers/delete.php [new file with mode: 0644]
fima/ledgers/edit.php [new file with mode: 0644]
fima/ledgers/index.php [new file with mode: 0644]
fima/lib/Block/summary.php [new file with mode: 0644]
fima/lib/Block/tree_menu.php [new file with mode: 0644]
fima/lib/Driver.php [new file with mode: 0644]
fima/lib/Driver/sql.php [new file with mode: 0644]
fima/lib/Fima.php [new file with mode: 0644]
fima/lib/Forms/CreateLedger.php [new file with mode: 0644]
fima/lib/Forms/DeleteLedger.php [new file with mode: 0644]
fima/lib/Forms/EditLedger.php [new file with mode: 0644]
fima/lib/Forms/account.php [new file with mode: 0644]
fima/lib/Report.php [new file with mode: 0644]
fima/lib/Report/AccountOverview.php [new file with mode: 0644]
fima/lib/Report/Analysis.php [new file with mode: 0644]
fima/lib/Report/AssetOverview.php [new file with mode: 0644]
fima/lib/Report/GeneralOverview.php [new file with mode: 0644]
fima/lib/Report/PeriodOverview.php [new file with mode: 0644]
fima/lib/Report/Trend.php [new file with mode: 0644]
fima/lib/ReportGraph.php [new file with mode: 0644]
fima/lib/ReportGraph/Bar.php [new file with mode: 0644]
fima/lib/ReportGraph/Line.php [new file with mode: 0644]
fima/lib/ReportGraph/Pie.php [new file with mode: 0644]
fima/lib/UI/VarRenderer/fima.php [new file with mode: 0644]
fima/lib/base.php [new file with mode: 0644]
fima/lib/prefs.php [new file with mode: 0644]
fima/lib/version.php [new file with mode: 0644]
fima/locale/de_DE/LC_MESSAGES/fima.mo [new file with mode: 0644]
fima/locale/de_DE/help.xml [new file with mode: 0644]
fima/locale/en_US/help.xml [new file with mode: 0644]
fima/po/README [new file with mode: 0644]
fima/po/de_DE.po [new file with mode: 0644]
fima/po/fima.pot [new file with mode: 0644]
fima/postings.php [new file with mode: 0644]
fima/report.php [new file with mode: 0644]
fima/scripts/sql/fima.sql [new file with mode: 0644]
fima/search.php [new file with mode: 0644]
fima/templates/accounts/accounts.inc [new file with mode: 0644]
fima/templates/common-header.inc [new file with mode: 0644]
fima/templates/data/export.inc [new file with mode: 0644]
fima/templates/data/import.inc [new file with mode: 0644]
fima/templates/ledgers_list.php [new file with mode: 0644]
fima/templates/menu.inc [new file with mode: 0644]
fima/templates/postings/actions.inc [new file with mode: 0644]
fima/templates/postings/edit.inc [new file with mode: 0644]
fima/templates/postings/empty.inc [new file with mode: 0644]
fima/templates/postings/footer.inc [new file with mode: 0644]
fima/templates/postings/header.inc [new file with mode: 0644]
fima/templates/postings/javascript_edit.inc [new file with mode: 0644]
fima/templates/postings/javascript_list.inc [new file with mode: 0644]
fima/templates/postings/javascript_shift.inc [new file with mode: 0644]
fima/templates/postings/javascript_transfer.inc [new file with mode: 0644]
fima/templates/postings/list.inc [new file with mode: 0644]
fima/templates/postings/navbar.inc [new file with mode: 0644]
fima/templates/postings/posting_footers.inc [new file with mode: 0644]
fima/templates/postings/posting_headers.inc [new file with mode: 0644]
fima/templates/postings/shift.inc [new file with mode: 0644]
fima/templates/postings/transfer.inc [new file with mode: 0644]
fima/templates/prefs/closedperiodselect.inc [new file with mode: 0644]
fima/templates/prefs/ledgerselect.inc [new file with mode: 0644]
fima/templates/reports/empty.inc [new file with mode: 0644]
fima/templates/reports/graph.inc [new file with mode: 0644]
fima/templates/reports/img.inc [new file with mode: 0644]
fima/templates/reports/reports.inc [new file with mode: 0644]
fima/templates/reports/table.inc [new file with mode: 0644]
fima/templates/search/search.inc [new file with mode: 0644]
fima/themes/bluewhite/screen.css [new file with mode: 0644]
fima/themes/graphics/accounts.png [new file with mode: 0644]
fima/themes/graphics/accounts.psd [new file with mode: 0644]
fima/themes/graphics/add.png [new file with mode: 0644]
fima/themes/graphics/asset.png [new file with mode: 0644]
fima/themes/graphics/eoasset.png [new file with mode: 0644]
fima/themes/graphics/eoexpense.png [new file with mode: 0644]
fima/themes/graphics/eoincome.png [new file with mode: 0644]
fima/themes/graphics/expense.png [new file with mode: 0644]
fima/themes/graphics/fima.png [new file with mode: 0644]
fima/themes/graphics/income.png [new file with mode: 0644]
fima/themes/graphics/list.png [new file with mode: 0644]
fima/themes/graphics/new-small.png [new file with mode: 0644]
fima/themes/graphics/posting.psd [new file with mode: 0644]
fima/themes/graphics/report.png [new file with mode: 0644]
fima/themes/graphics/reports.psd [new file with mode: 0644]
fima/themes/graphics/search-small.png [new file with mode: 0644]
fima/themes/report.inc [new file with mode: 0644]
fima/themes/screen.css [new file with mode: 0644]
fima/themes/silver/graphics/accounts.png [new file with mode: 0644]
fima/themes/silver/graphics/accounts.psd [new file with mode: 0644]
fima/themes/silver/graphics/add.png [new file with mode: 0644]
fima/themes/silver/graphics/asset.png [new file with mode: 0644]
fima/themes/silver/graphics/eoasset.png [new file with mode: 0644]
fima/themes/silver/graphics/eoexpense.png [new file with mode: 0644]
fima/themes/silver/graphics/eoincome.png [new file with mode: 0644]
fima/themes/silver/graphics/expense.png [new file with mode: 0644]
fima/themes/silver/graphics/fima.png [new file with mode: 0644]
fima/themes/silver/graphics/income.png [new file with mode: 0644]
fima/themes/silver/graphics/list.png [new file with mode: 0644]
fima/themes/silver/graphics/new-small.png [new file with mode: 0644]
fima/themes/silver/graphics/posting.psd [new file with mode: 0644]
fima/themes/silver/graphics/report.png [new file with mode: 0644]
fima/themes/silver/graphics/report.psd [new file with mode: 0644]
fima/themes/silver/graphics/search-small.png [new file with mode: 0644]
fima/themes/silver/screen.css [new file with mode: 0644]
fima/themes/silver/themed_graphics [new file with mode: 0644]

diff --git a/fima/COPYING b/fima/COPYING
new file mode 100644 (file)
index 0000000..5a965fb
--- /dev/null
@@ -0,0 +1,280 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
diff --git a/fima/README b/fima/README
new file mode 100644 (file)
index 0000000..13396bb
--- /dev/null
@@ -0,0 +1,88 @@
+What is Fima?
+=================
+
+:Last update:   $Date: 2008/08/25 22:24:53 $
+:Revision:      $Revision: 1.0 $
+
+.. contents:: Contents
+.. section-numbering::
+
+Fima is a double entry based ledger written in PHP and utilizing the Horde
+Application Framework.
+
+This software is OSI Certified Open Source Software. OSI Certified is a
+certification mark of the `Open Source Initiative`_.
+
+.. _`Open Source Initiative`: http://www.opensource.org/
+
+
+Obtaining Fima
+------------------
+
+Further information on Fima and the latest version can be obtained at
+
+  http://www.horde.org/fima/
+
+
+Documentation
+-------------
+
+The following documentation is available in the Fima distribution:
+
+:README_:            This file
+:COPYING_:           Copyright and license information
+:LICENSE_:           Copyright and license information
+:`docs/CHANGES`_:    Changes by release
+:`docs/CREDITS`_:    Project developers
+:`docs/INSTALL`_:    Installation instructions and notes
+:`docs/TODO`_:       Development TODO list
+:`docs/UPGRADING`_:  Pointers on upgrading from previous Fima versions
+
+
+Installation
+------------
+
+Instructions for installing Fima can be found in the file INSTALL_ in the
+``docs/`` directory of the Fima distribution.
+
+
+Assistance
+----------
+
+If you encounter problems with Fima, help is available!
+
+The Horde Frequently Asked Questions List (FAQ), available on the Web at
+
+  http://www.horde.org/faq/
+
+The Horde Project runs a number of mailing lists, for individual applications
+and for issues relating to the project as a whole. Information, archives, and
+subscription information can be found at
+
+  http://www.horde.org/mail/
+
+Lastly, Horde developers, contributors and users also make occasional
+appearances on IRC, on the channel #horde on the freenode Network
+(irc.freenode.net).
+
+
+Licensing
+---------
+
+For licensing and copyright information, please see the file COPYING_/LICENSE_
+in the Fima distribution.
+
+Thanks,
+
+The Fima team
+
+
+.. _README: ?f=README.html
+.. _COPYING: http://www.horde.org/licenses/gpl.php
+.. _LICENSE: http://www.horde.org/licenses/asl.php
+.. _docs/CHANGES: ?f=CHANGES.html
+.. _docs/CREDITS: ?f=CREDITS.html
+.. _INSTALL:
+.. _docs/INSTALL: ?f=INSTALL.html
+.. _docs/TODO: ?f=TODO.html
+.. _docs/UPGRADING: ?f=UPGRADING.html
diff --git a/fima/account.php b/fima/account.php
new file mode 100644 (file)
index 0000000..9055758
--- /dev/null
@@ -0,0 +1,240 @@
+<?php
+/**
+ * $Horde: fima/account.php,v 1.1 2009/03/11 17:13:00 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file COPYING for license information (GPL).  If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('FIMA_BASE', dirname(__FILE__));
+require_once FIMA_BASE . '/lib/base.php';
+require_once FIMA_BASE . '/lib/Forms/account.php';
+$vars = Variables::getDefaultVariables();
+
+/* Redirect to the account list if no action has been requested. */
+$actionID = $vars->get('actionID');
+if (is_null($actionID)) {
+    header('Location: ' . Horde::applicationUrl('accounts.php', true));
+    exit;
+}
+
+/* Get ledger. */
+$ledger = Fima::getActiveLedger();
+$share = &$GLOBALS['fima_shares']->getShare($ledger);
+if (is_a($share, 'PEAR_Error')) {
+    $notification->push(sprintf(_("Access denied on account: %s"), $share->getMessage()), 'horde.error');
+    header('Location: ' . Horde::applicationUrl('accounts.php', true));
+    exit;
+}
+$ledger_name = $share->get('name');
+
+/* Run through the action handlers. */
+switch ($actionID) {
+case 'add_account':
+    $vars->set('actionID', 'save_account');
+
+    /* Preset account attributes regarding its parent. */
+    $parent_id = $vars->get('account');
+    $vars->set('parent_id', $parent_id);
+    if (isset($parent_id)) {
+        $account_types = Fima::getAccountTypes();
+        if (isset($account_types[$parent_id])) {
+            $vars->set('type', $parent_id);
+        } else {
+            $parent = Fima::getAccount($parent_id);
+            if (!is_a($parent, 'PEAR_Error')) {
+                if (Fima::getAccountParent($parent['number']) === null) {
+                    $accounts = Fima::listAccounts();
+                    $tmp = '';
+                    foreach ($accounts as $accountId => $account) {
+                        if ((int)$account['number'] >= $parent['number'] + 100) {
+                            break;
+                        }
+                        $tmp = $account['number'] + 1;
+                    }
+                    if (Fima::getAccountParent($tmp) == $parent['number']) {
+                        $vars->set('number', sprintf('%\'04d', $tmp));
+                    }
+                }
+                $vars->set('type', $parent['type']);
+            }
+        }
+    }
+    $vars->set('number_new', $vars->get('number'));
+    
+    $form = new Fima_AccountForm($vars, _("New Account"));
+    break;
+
+case 'modify_account':
+    $account_id = $vars->get('account');
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+        $notification->push(_("Access denied editing account."), 'horde.error');
+    } else {
+        $account = Fima::getAccount($account_id);
+        if (!isset($account) || !isset($account['account_id'])) {
+            $notification->push(_("Account not found."), 'horde.error');
+        } else {
+            $vars = new Variables($account);
+            $vars->set('actionID', 'save_account');
+            $vars->set('number_new', $vars->get('number'));
+            $form = new Fima_AccountForm($vars, sprintf(_("Edit: %s"), trim($account['number'] . ' ' . $account['name'])), $share->hasPermission(Auth::getAuth(), PERMS_DELETE));
+            break;
+        }
+    }
+
+    /* Return to the accounts. */
+    header('Location: ' . Horde::applicationUrl('accounts.php', true));
+    exit;
+
+case 'save_account':
+    if ($vars->get('submitbutton') == _("Delete this account")) {
+        /* Redirect to the delete form. */
+        $account_id = $vars->get('account_id');
+        header('Location: ' . Util::addParameter(Horde::applicationUrl('account.php', true), array('account' => $account_id, 'actionID' => 'delete_account'), null, false));
+        exit;
+    }
+
+    $form = new Fima_AccountForm($vars, $vars->get('account_id') ? sprintf(_("Edit: %s"), $vars->get('name')) : _("New Account"));
+    if (!$form->validate($vars)) {
+        break;
+    }
+
+    $form->getInfo($vars, $info);
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+        $notification->push(sprintf(_("Access denied saving account to %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('accounts.php', true));
+        exit;
+    }
+
+    $storage = &Fima_Driver::singleton($ledger);
+    $info['number_new'] = sprintf('%\'04d', $info['number_new']);
+
+    /* Check for existing account width provided number. */
+    if ($info['number'] != $info['number_new']) {
+        $existingaccount = $storage->getAccountByNumber($info['number_new']);
+        if (!is_a($existingaccount, 'PEAR_Error')) {
+            $notification->push(sprintf(_("The account number %s is already used by the account %s."), $info['number_new'], trim($existingaccount['number'] . ' ' . $existingaccount['name'])), 'horde.error');
+            break;
+        } else {
+            $notification->push(sprintf(_("The account including all postings was shifted from number %s to %s."), $info['number'], $info['number_new']), 'horde.message');
+        }
+    }
+    
+    /* Check account type. */
+    if (($parent_number = Fima::getAccountParent($info['number_new'])) !== null) {
+        $parent = $storage->getAccountByNumber($parent_number);
+        if (!is_a($parent, 'PEAR_Error')) {
+            if ($info['type'] != $parent['type']) {
+                $info['type'] = $parent['type'];
+                $notification->push(sprintf(_("The account type was set to %s."), Fima::getAccountTypes($info['type'])), 'horde.message');
+            }
+        }
+    }
+
+    /* If an account id is set, we're modifying an existing account.
+     * Otherwise, we're adding a new account with the provided
+     * attributes. */
+    if (!empty($info['account_id'])) {
+        $result = $storage->modifyAccount($info['account_id'],
+                                          $info['number_new'],
+                                          $info['type'],
+                                          $info['name'],
+                                          $info['eo'],
+                                          $info['desc'],
+                                          $info['closed']);
+    } else {
+        $result = $storage->addAccount($info['number_new'],
+                                       $info['type'],
+                                       $info['name'],
+                                       $info['eo'],
+                                       $info['desc'],
+                                       $info['closed']);
+    }
+
+    /* Check our results. */
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push(sprintf(_("There was a problem saving the account: %s."), $result->getMessage()), 'horde.error');
+    } else {
+        $notification->push(sprintf(_("Saved %s."), trim($info['number_new'] . ' ' . $info['name'])), 'horde.success');
+        /* Return to the accounts. */
+        if ($vars->get('submitbutton') == _("Save and New")) {
+            header('Location: ' . Util::addParameter(Horde::applicationUrl('account.php', true), array('account' => $vars->get('parent_id'), 'actionID' => 'add_account'), null, false));
+        } else {
+            header('Location: ' . Horde::applicationUrl('accounts.php', true));
+        }
+        exit;
+    }
+
+    break;
+
+case 'delete_account':
+    $account_id = $vars->get('account');
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_DELETE)) {
+        $notification->push(_("Access denied deleting account."), 'horde.error');
+    } else {
+        $account = Fima::getAccount($account_id);
+        if (!isset($account) || !isset($account['account_id'])) {
+            $notification->push(_("Account not found."), 'horde.error');
+        } else {
+            $vars = new Variables($account);
+            $vars->set('actionID', 'purge_account');
+            $vars->set('dssubaccounts', array('type' => 'none', 'account' => $account_id));
+            $vars->set('dspostings', array('type' => 'delete', 'account' => $account_id));
+            $form = new Fima_AccountDeleteForm($vars, sprintf(_("Delete: %s"), trim($account['number'] . ' ' . $account['name'])), $share->hasPermission(Auth::getAuth(), PERMS_EDIT));
+            break;
+        }
+    }
+
+    /* Return to the accounts. */
+    header('Location: ' . Horde::applicationUrl('accounts.php', true));
+    exit;
+
+case 'purge_account':
+    if ($vars->get('submitbutton') == _("Edit this account")) {
+        /* Redirect to the edit form. */
+        $account_id = $vars->get('account_id');
+        header('Location: ' . Util::addParameter(Horde::applicationUrl('account.php', true), array('account' => $account_id, 'actionID' => 'modify_account'), null, false));
+        exit;
+    }
+
+    $form = new Fima_AccountDeleteForm($vars, sprintf(_("Delete: %s"), trim($vars->get('number') . ' ' . $vars->get('name'))));
+    if (!$form->validate($vars)) {
+        break;
+    }
+
+    $form->getInfo($vars, $info);
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_DELETE)) {
+        $notification->push(sprintf(_("Access denied deleting account from %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('accounts.php', true));
+        exit;
+    }
+
+    $storage = &Fima_Driver::singleton($ledger);
+
+    /* Delete the account. */
+    $result = $storage->deleteAccount($info['account_id'], $info['dssubaccounts'], $info['dspostings']);
+
+    /* Check our results. */
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push(sprintf(_("There was a problem deleting the account: %s."), $result->getMessage()), 'horde.error');
+    } else {
+        $notification->push(sprintf(_("Deleted %s."), trim($info['number_new'] . ' ' . $info['name'])), 'horde.success');
+        /* Return to the accounts. */
+        header('Location: ' . Horde::applicationUrl('accounts.php', true));
+        exit;
+    }
+
+    break;
+
+default:
+    header('Location: ' . Horde::applicationUrl('accounts.php', true));
+    exit;
+}
+
+$title = $form->getTitle();
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+$form->renderActive();
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/accounts.php b/fima/accounts.php
new file mode 100644 (file)
index 0000000..d903439
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * $Horde: fima/accounts.php,v 1.0 2009/02/18 07:29:22 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Thomas Trethan <thomas@trethan.net>
+ */
+
+@define('FIMA_BASE', dirname(__FILE__));
+require_once FIMA_BASE . '/lib/base.php';
+require_once 'Horde/Tree.php';
+
+/* Get ledger. */
+$ledger = Fima::getActiveLedger();
+$share = &$GLOBALS['fima_shares']->getShare($ledger);
+if (is_a($share, 'PEAR_Error')) {
+    $notification->push(sprintf(_("Access denied on accounts: %s"), $share->getMessage()), 'horde.error');
+}
+
+/* Run through the action handlers. */
+$actionID = Util::getFormData('actionID');
+switch ($actionID) {
+case 'delete_all':
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_DELETE)) {
+        $notification->push(_("Access denied deleting all accounts and postings."), 'horde.error');
+    } else {
+        $storage = &Fima_Driver::singleton($ledger);
+    
+        /* Delete all. */
+        $result = $storage->deleteAll();
+        if (is_a($result, 'PEAR_Error')) {
+            $notification->push(sprintf(_("There was a problem deleting all accounts and postings: %s"),
+                                        $result->getMessage()), 'horde.error');
+        } else {
+            $notification->push(_("Deleted all accounts and postings."), 'horde.success');
+        }
+        break;
+    }
+default:
+    break;
+}
+
+/* Prepare account folder structure */
+$account_url = Horde::applicationUrl('account.php');;
+$view_url = Util::addParameter(Horde::applicationUrl('postings.php'), 'actionID', 'search_postings');
+
+$accounts = array();
+$accounts['root'] = array('account_id' => 'root', 'owner' => $ledger, 'number' => '', 'type' => 'root', 'name' => $share->get('name'), 'desc' => '', 'icon' => 'accounts.png', 'closed' => false, 'expanded' => true, 'parent_id' => null);
+
+$types = Fima::getAccountTypes();
+foreach ($types as $typeId => $typeLabel) {
+    $accounts[$typeId] = array('account_id' => $typeId, 'owner' => $ledger, 'number' => '', 'type' => $typeId, 'name' => $typeLabel, 'desc' => '', 'icon' => $typeId . '.png', 'closed' => false, 'expanded' => true, 'parent_id' => 'root', 'view_link' => Util::addParameter($view_url, 'search_type', $typeId), 'add_link' => Util::addParameter($account_url, array('account' => $typeId, 'actionID' => 'add_account')));
+}
+
+/* Get accounts. */
+$accountlist = Fima::listAccounts();
+foreach ($accountlist as $accountId => $account) {
+    $accounts[$accountId] = $account;
+    
+    $accounts[$accountId]['view_link'] = Util::addParameter($view_url, $account['type'] == FIMA_ACCOUNTTYPE_ASSET ? 'search_asset' : 'search_account', $account['account_id']);
+    $account_url_account = Util::addParameter($account_url, 'account', $account['account_id']);
+    $accounts[$accountId]['add_link'] = Util::addParameter($account_url_account, 'actionID', 'add_account');
+    $accounts[$accountId]['edit_link'] = Util::addParameter($account_url_account, 'actionID', 'modify_account');
+    $accounts[$accountId]['delete_link'] = Util::addParameter($account_url_account, 'actionID', 'delete_account');
+    
+    if ($account['parent_id'] !== null && isset($accounts[$account['parent_id']])) {
+        unset($accounts[$accountId]['add_link']);
+    } else {
+        $accounts[$accountId]['parent_id'] = $account['type'];
+        $accounts[$accountId]['parent_number'] = '';
+        $accounts[$accountId]['parent_name'] = '';
+    }
+      
+    $accounts[$accountId]['icon'] = $accounts[$accounts[$accountId]['parent_id']]['icon'];
+    $accounts[$accountId]['closed'] = $account['closed'];
+    $accounts[$accountId]['expanded'] = false;
+}
+
+/* Print. */
+$print_view = (bool)Util::getFormData('print');
+if (!$print_view) {
+    Horde::addScriptFile('popup.js', 'horde', true);
+    $print_link = Horde::applicationUrl(Util::addParameter('accounts.php', array('print' => 1)));
+}
+
+Horde::addScriptFile('tables.js', 'horde', true);
+$title = _("My Accounts");
+require FIMA_TEMPLATES . '/common-header.inc';
+
+if ($print_view) {
+    require_once $registry->get('templates', 'horde') . '/javascript/print.js';
+} else {
+    require FIMA_TEMPLATES . '/menu.inc';
+}
+
+require FIMA_TEMPLATES . '/accounts/accounts.inc';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/config/.htaccess b/fima/config/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/fima/config/conf.xml b/fima/config/conf.xml
new file mode 100644 (file)
index 0000000..77e14ee
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!-- $Horde: fima/config/conf.xml,v 1.0 2008/04/24 18:49:43 trt Exp $ -->
+<configuration>
+ <configsection name="storage">
+  <configheader>Storage System Settings</configheader>
+
+  <configswitch name="driver" desc="What storage driver should we use?">sql
+   <case name="sql" desc="SQL">
+    <configsection name="params">
+     <configsql switchname="driverconfig">
+      <configstring name="table_accounts" desc="Database table accounts">fima_accounts</configstring>
+      <configstring name="table_postings" desc="Database table postings">fima_postings</configstring>
+     </configsql>
+    </configsection>
+   </case>
+  </configswitch>
+ </configsection>
+
+ <configsection name="menu">
+  <configheader>Menu Settings</configheader>
+  <configmultienum name="apps" desc="Select any applications that should be linked in the Fima menu">
+   <values>
+    <configspecial name="list-horde-apps" />
+   </values>
+  </configmultienum>
+ </configsection>
+</configuration>
diff --git a/fima/config/menu.php.dist b/fima/config/menu.php.dist
new file mode 100644 (file)
index 0000000..d702a2c
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * $Horde: fima/config/menu.php.dist,v 1.0 2008/04/24 19:03:15 trt Exp $
+ *
+ * This file lets you extend Fima's menu with your own items.
+ *
+ * To add a new menu item, simply add a new entry to the $_menu array.
+ * Valid attributes for a new menu item are:
+ *
+ *  'url'       The URL value for the menu item.
+ *  'text'      The text to accompany the menu item.
+ *
+ * These attributes are optional:
+ *
+ *  'icon'      The filename of an icon to use for the menu item.
+ *  'icon_path' The path to the icon if it doesn't exist in the graphics/
+ *              directory.
+ *  'target'    The "target" of the link (e.g. '_top', '_blank').
+ *  'onclick'   Any JavaScript to execute on the "onclick" event.
+ *
+ * Here's an example entry:
+ *
+ *  $_menu[] = array(   
+ *      'url' =>        'http://www.example.com/',
+ *      'text' =>       'Example, Inc.',
+ *      'icon' =>       'example.png',
+ *      'icon_path' =>  'http://www.example.com/images/',
+ *      'target' =>     '_blank',
+ *      'onclick' =>    ''
+ *  );
+ *
+ * You can also add a "separator" (a spacer) between menu items.  To add a
+ * separator, simply add a new string to the $_menu array set to the text
+ * 'separator'.  It should look like this:
+ *
+ *  $_menu[] = 'separator';
+ */
+
+$_menu = array();
+
+/* Add your custom entries below this line. */
diff --git a/fima/config/prefs.php.dist b/fima/config/prefs.php.dist
new file mode 100644 (file)
index 0000000..59fc274
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+/**
+ * $Horde: fima/config/prefs.php.dist,v 1.1 2008/04/28 09:28:14 trt Exp $
+ *
+ * See horde/config/prefs.php for documentation on the structure of this file.
+ */
+
+// Make sure that constants are defined.
+@define('FIMA_BASE', '/usr/share/horde/fima');
+require_once FIMA_BASE . '/lib/Fima.php';
+
+$prefGroups['share'] = array(
+    'column' => _("General Options"),
+    'label' => _("Active Configuration"),
+    'desc' => _("Choose your active Ledger and Posting Type."),
+    'members' => array('ledgerselect', 'active_postingtype', 'closedperiodselect')
+);
+
+$prefGroups['display'] = array(
+    'column' => _("General Options"),
+    'label' => _("Interface Options"),
+    'desc' => _("Change the display and input options."),
+    'members' => array('max_postings', 'startpage', 'sortby', 'altsortby', 'sortdir', 'wildcard_format', 'amount_format', 'expenses_sign', 'delete_opt', 'report_graphsize')
+);
+
+// active ledger selection widget
+$_prefs['ledgerselect'] = array('type' => 'special');
+
+// active ledger
+// Set locked to true if you don't want users to have multiple ledgers.
+$_prefs['active_ledger'] = array(
+    'value' => Auth::getAuth() ? Auth::getAuth() : 0,
+    'locked' => false,
+    'shared' => true,
+    'type' => 'implicit'
+);
+
+// store the ledgers to diplay
+$_prefs['display_ledgers'] = array(
+    'value' => 'a:0:{}',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'implicit'
+);
+
+// active posting type
+$_prefs['active_postingtype'] = array(
+    'value' => FIMA_POSTINGTYPE_ACTUAL,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array(FIMA_POSTINGTYPE_ACTUAL => _("Actual"),
+                    FIMA_POSTINGTYPE_FORECAST => _("Forecast"),
+                    FIMA_POSTINGTYPE_BUDGET => _("Budget")),
+    'desc' => _("Your active posting type:")
+);
+
+// closed period selection widget
+$_prefs['closedperiodselect'] = array('type' => 'special');
+
+// closed period
+$_prefs['closed_period'] = array(
+    'value' => 0,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'implicit'
+);
+
+// postings per page
+$_prefs['max_postings'] = array(
+    'value' => 20,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'number',
+    'desc' => _("Postings per page in the list view.")
+);
+
+// start page
+$_prefs['startpage'] = array(
+    'value' => -1,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array(1 => _("First Page"),
+                    -1 => _("Last Page")),
+    'desc' => _("When displaying the postings, which page do you want to start on?")
+);
+
+// user preferred sorting column
+$_prefs['sortby'] = array(
+    'value' => FIMA_SORT_DATE,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array(FIMA_SORT_DATE => _("Date"),
+                    FIMA_SORT_ASSET => _("Asset Account"),
+                    FIMA_SORT_ACCOUNT => _("Posting Account"),
+                    FIMA_SORT_AMOUNT => _("Amount"),
+                    FIMA_SORT_DESC => _("Description")),
+    'desc' => _("Sort postings by:")
+);
+
+// alternate sort column
+$_prefs['altsortby'] = array(
+    'value' => FIMA_SORT_ACCOUNT,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array(FIMA_SORT_DATE => _("Date"),
+                    FIMA_SORT_ASSET => _("Asset Account"),
+                    FIMA_SORT_ACCOUNT => _("Posting Account"),
+                    FIMA_SORT_AMOUNT => _("Amount"),
+                    FIMA_SORT_DESC => _("Description")),
+    'desc' => _("Then:")
+);
+
+// user preferred sorting direction
+$_prefs['sortdir'] = array(
+    'value' => FIMA_SORT_ASCEND,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array(FIMA_SORT_ASCEND => _("Ascending"),
+                    FIMA_SORT_DESCEND => _("Descending")),
+    'desc' => _("Sort direction:")
+);
+
+// format for wildcards
+$_prefs['wildcard_format'] = array(
+    'value' => 'dos',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array('dos' => _("DOS (* and ?)"),
+                    'sql' => _("SQL (% and _)"),
+                    'none' => _("none")),
+    'desc' => _("Select the format for wildcards for text search:")
+);
+
+// format for amounts
+$_prefs['amount_format'] = array(
+    'value' => '.,',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array('.,' => _("-12.345.678,90"),
+                    ',.' => _("-12,345,678.90"),
+                    ' ,' => _("-12 345 678,90"),
+                    '\'.' => _("-12'345'678.90")),
+    'desc' => _("Select the format for amounts:")
+);
+
+// sign for expenses
+$_prefs['expenses_sign'] = array(
+    'value' => 0,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'checkbox',
+    'desc' => _("Enter expenses with negative sign?")
+);
+
+// preference for delete confirmation dialog.
+$_prefs['delete_opt'] = array(
+    'value' => 1,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'checkbox',
+    'desc' => _("Do you want to confirm deleting postings?")
+);
+
+// report graph size
+$_prefs['report_graphsize'] = array(
+    'value' => '800x600',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array('400x300'  => _("400 x 300 Pixel"),
+                    '800x600'  => _("800 x 600 Pixel"),
+                    '1024x768' => _("1024 x 768 Pixel"),
+                    '1600x1200' => _("1600 x 1200 Pixel")),
+    'desc' => _("Select the canvas size for chart reports:")
+);
diff --git a/fima/config/report.php.dist b/fima/config/report.php.dist
new file mode 100644 (file)
index 0000000..42410e7
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * $Horde: fima/config/report.php.dist,v 1.0 2008/08/24 13:35:20 trt Exp $
+ *
+ * This file will defines the set of existing reports.
+ * Reports are located in fima/lib/Report/.
+ */
+
+$_reports = array('GeneralOverview' => _("General Overview"),
+                  'PeriodOverview'  => _("Period Overview"),
+                  'AccountOverview' => _("Account Overview"),
+                  'AssetOverview'      => _("Asset Overview"),
+                  'Analysis'        => _("Analysis"),
+                  'Trend'           => _("Trend"),
+                  );
diff --git a/fima/data.php b/fima/data.php
new file mode 100644 (file)
index 0000000..3f8a5ef
--- /dev/null
@@ -0,0 +1,234 @@
+<?php
+/**
+ * $Horde: fima/data.php,v 1.0 2008/05/19 19:39:18 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ */
+
+function _cleanup()
+{
+    global $import_step;
+    $import_step = 1;
+    return IMPORT_FILE;
+}
+
+@define('FIMA_BASE', dirname(__FILE__));
+require_once FIMA_BASE . '/lib/base.php';
+require_once 'Horde/Data.php';
+
+$ledger = Fima::getActiveLedger();
+
+/* Importable file types. */
+$file_types = array('csv'      => _("CSV"),
+                    'tsv'      => _("TSV"));
+
+/* Templates for the different import steps. */
+$templates = array(
+    IMPORT_CSV => array($registry->get('templates', 'horde') . '/data/csvinfo.inc'),
+    IMPORT_TSV => array($registry->get('templates', 'horde') . '/data/tsvinfo.inc'),
+    IMPORT_MAPPED => array($registry->get('templates', 'horde') . '/data/csvmap.inc'),
+    IMPORT_DATETIME => array($registry->get('templates', 'horde') . '/data/datemap.inc'),
+    IMPORT_FILE => array(FIMA_TEMPLATES . '/data/import.inc', FIMA_TEMPLATES . '/data/export.inc'),
+);
+
+/* Field/clear name mapping. */
+$app_fields = array('date'     => _("Date"),
+                    'asset'    => _("Asset Account"),
+                    'account'  => _("Account"),
+                    'desc'     => _("Description"),
+                    'amount'   => _("Amount"),
+                    'eo'       => _("e.o."));
+
+/* Date/time fields. */
+$time_fields = array('date' => 'date');
+
+/* Initial values. */
+$param = array('time_fields' => $time_fields,
+               'file_types'  => $file_types);
+$import_format = Util::getFormData('import_format', '');
+$import_step   = Util::getFormData('import_step', 0) + 1;
+$next_step     = IMPORT_FILE;
+$actionID      = Util::getFormData('actionID');
+$error         = false;
+
+/* Loop through the action handlers. */
+switch ($actionID) {
+case 'export':
+    $data = array();
+    
+    /* Create a Fima storage instance. */
+    $storage = &Fima_Driver::singleton($ledger);
+    if (is_a($storage, 'PEAR_Error')) {
+        $notification->push(sprintf(_("Failed to access the ledger: %s"), $storage->getMessage()), 'horde.error');
+        $error = true;
+        break;
+    }
+    $params = $storage->getParams();
+    
+    $filters = array(array('type', $prefs->getValue('active_postingtype')));
+
+    /* Get accounts and postings. */
+    $accounts = Fima::listAccounts();
+    $postings = Fima::listPostings($filters);
+   
+    foreach ($postings as $postingId => $posting) {
+        $row = array();
+        foreach ($posting as $key => $value) {
+            switch ($key) {
+            case 'date':
+                $row[$key] = strftime(Fima::convertDateFormat($prefs->getValue('date_format')), $value);
+                break;
+            case 'asset':
+            case 'account':
+                $row[$key] = isset($accounts[$value]) ? $accounts[$value]['number'] : '';
+                break;
+            case 'amount':
+                $row[$key] = Fima::convertValueToAmount($value);
+                break;
+            case 'eo':
+            case 'desc':            
+                $row[$key] = String::convertCharset($value, NLS::getCharset(), $params['charset']);
+                break;
+            default:
+                break;
+            }
+        }
+        $data[] = $row;
+    }
+
+    if (!count($data)) {
+        $notification->push(_("There were no postings to export."), 'horde.message');
+        $error = true;
+        break;
+    }
+
+    switch (Util::getFormData('exportID')) {
+    case EXPORT_CSV:
+        $csv = &Horde_Data::singleton('csv');
+        $csv->exportFile(_("postings.csv"), $data, true);
+        exit;
+
+    case EXPORT_TSV:
+        $tsv = &Horde_Data::singleton('tsv');
+        $tsv->exportFile(_("postings.tsv"), $data, true);
+        exit;
+    }
+    break;
+
+case IMPORT_FILE:
+    $storage = &Fima_Driver::singleton($ledger);
+    if (is_a($storage, 'PEAR_Error')) {
+        $notification->push(sprintf(_("Failed to access the ledger: %s"), $storage->getMessage()), 'horde.error');
+        $error = true;
+        break;
+    }
+
+    $_SESSION['import_data']['target'] = $ledger;
+    $_SESSION['import_data']['purge'] = Util::getFormData('purge');
+    break;
+}
+
+if (!$error) {
+    $data = &Horde_Data::singleton($import_format);
+    if (is_a($data, 'PEAR_Error')) {
+        $notification->push(_("This file format is not supported."), 'horde.error');
+        $next_step = IMPORT_FILE;
+    } else {
+        $next_step = $data->nextStep($actionID, $param);
+        if (is_a($next_step, 'PEAR_Error')) {
+            $notification->push($next_step->getMessage(), 'horde.error');
+            $next_step = $data->cleanup();
+        }
+    }
+}
+
+/* We have a final result set. */
+if (is_array($next_step)) {
+    /* Create a Fima storage instance. */
+    $storage = &Fima_Driver::singleton($ledger);
+    if (is_a($storage, 'PEAR_Error')) {
+        $notification->push(sprintf(_("Failed to access the ledger: %s"), $storage->getMessage()), 'horde.error');
+    }
+
+    $params = $storage->getParams();
+
+    /* Purge old postings if requested. */
+    if ($_SESSION['import_data']['purge']) {
+        $result = $storage->deleteAll(false, $prefs->getValue('active_postingtype'));
+        if (is_a($result, 'PEAR_Error')) {
+            $notification->push(sprintf(_("The postings could not be purged: %s"), $result->getMessage()), 'horde.error');
+        } else {
+            $notification->push(_("Postings successfully purged."), 'horde.success');
+        }
+    }
+
+    /* Get accounts and postings. */
+    $accounts = Fima::listAccounts();
+    $accounts_indices = array();
+    foreach ($accounts as $account) {
+        $accounts_indices[$account['number']] = $account['account_id'];
+    }
+
+    foreach ($next_step as $row) {
+        $row['type'] = $prefs->getValue('active_postingtype');
+        $row['asset'] = sprintf('%\'04d', $row['asset']);
+        $row['asset'] = isset($accounts_indices[$row['asset']]) ? $accounts_indices[$row['asset']] : null;
+        $row['account'] = sprintf('%\'04d', $row['account']);
+        $row['account'] = isset($accounts_indices[$row['account']]) ? $accounts_indices[$row['account']] : null;
+        $row['date'] = Fima::convertDateToStamp($row['date'], Fima::convertDateFormat($prefs->getValue('date_format')));
+        $row['amount'] = Fima::convertAmountToValue($row['amount']);
+        if ($prefs->getValue('expenses_sign') == 0) {
+            if ($row['account'] !== null) {
+                if ($accounts[$row['account']]['type'] == FIMA_ACCOUNTTYPE_EXPENSE) {
+                    $row['amount'] *= -1;
+                }
+            } else {
+                $row['amount'] *= -1;
+            }
+        }
+        $row['desc'] = isset($row['desc']) ? trim($row['desc']) : '';
+        $row['eo'] = isset($row['eo']) ? (bool)trim($row['eo']) : false;
+        $result = $storage->addPosting($row['type'], $row['date'], $row['asset'], $row['account'], $row['eo'], $row['amount'], $row['desc']);
+        if (is_a($result, 'PEAR_Error')) {
+            break;
+        }
+    }
+
+    if (!count($next_step)) {
+        $notification->push(sprintf(_("The %s file didn't contain any postings."),
+                                    $file_types[$_SESSION['import_data']['format']]), 'horde.error');
+    } elseif (is_a($result, 'PEAR_Error')) {
+        $notification->push(sprintf(_("There was an error importing the data: %s"),
+                                    $result->getMessage()), 'horde.error');
+    } else {
+        $notification->push(sprintf(_("%s successfully imported"),
+                                    $file_types[$_SESSION['import_data']['format']]), 'horde.success');
+    }
+    $next_step = $data->cleanup();
+}
+
+$title = _("Import/Export Postings");
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+
+if ($next_step == IMPORT_FILE) {
+    /* Build the charset options. */
+    $charsets = $nls['encodings'];
+    $all_charsets = $nls['charsets'];
+    natcasesort($all_charsets);
+    foreach ($all_charsets as $charset) {
+        if (!isset($charsets[$charset])) {
+            $charsets[$charset] = $charset;
+        }
+    }
+    $my_charset = NLS::getCharset(true);
+}
+
+foreach ($templates[$next_step] as $template) {
+    require $template;
+    echo '<br />';
+}
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/docs/CHANGES b/fima/docs/CHANGES
new file mode 100644 (file)
index 0000000..653ced5
--- /dev/null
@@ -0,0 +1,12 @@
+---
+v1.1
+---
+
+[trt] Close accounts.
+[trt] Hide null rows in reports.
+
+---
+v1.0
+---
+
+[trt] Initial Release.
diff --git a/fima/docs/CREDITS b/fima/docs/CREDITS
new file mode 100644 (file)
index 0000000..65c8ce6
--- /dev/null
@@ -0,0 +1,26 @@
+===========================
+ Fima Development Team
+===========================
+
+
+Core Developers
+===============
+
+- Thomas Trethan <thomas@trethan.net>
+
+
+Localization
+============
+
+=====================   ======================================================
+German                  Thomas Trethan <thomas@trethan.net>
+                        Gerhard Trethan
+=====================   ======================================================
+
+
+Contributions
+=============
+
+- Gerhard Trethan, who inspired and supported the work on fima and had
+  programmed the basis of fima a while ago, a double entry based ledger in
+  visual basic.
diff --git a/fima/docs/INSTALL b/fima/docs/INSTALL
new file mode 100644 (file)
index 0000000..75f57fb
--- /dev/null
@@ -0,0 +1,231 @@
+=========================
+ Installing Fima 1.0
+=========================
+
+:Last update:   $Date: 2008/08/25 22:33:26 $
+:Revision:      $Revision: 1.0 $
+
+.. contents:: Contents
+.. section-numbering::
+
+This document contains instructions for installing Fima.
+
+For information on the capabilities and features of Skeleton, see the file
+README_ in the top-level directory of the Skeleton distribution.
+
+
+Obtaining Fima
+==================
+
+Fima can be obtained from the Horde website and FTP server, at
+
+   http://www.horde.org/fima/
+
+   ftp://ftp.horde.org/pub/fima/
+
+Or use the mirror closest to you:
+
+   http://www.horde.org/mirrors.php
+
+Bleeding-edge development versions of Fima are available via CVS; see the
+file `horde/docs/HACKING`_ in the Horde distribution, or the website
+http://www.horde.org/source/, for information on accessing the Horde CVS
+repository.
+
+
+Prerequisites
+=============
+
+To function properly, Fima **requires** the following:
+
+1. A working Horde installation.
+
+   Fima runs within the `Horde Application Framework`_, a set of common
+   tools for Web applications written in PHP. You must install Horde before
+   installing Fima.
+
+   .. Important:: Fima 1.0 requires version 3.0+ of the Horde Framework -
+                  earlier versions of Horde will **not** work.
+
+   .. _`Horde Application Framework`: http://www.horde.org/horde/
+
+   The Horde Framework can be obtained from the Horde website and FTP server,
+   at
+
+      http://www.horde.org/horde/
+
+      ftp://ftp.horde.org/pub/horde/
+
+   Many of Fima's prerequisites are also Horde prerequisites.
+
+   .. Important:: Be sure to have completed all of the steps in the
+                  `horde/docs/INSTALL`_ file for the Horde Framework before
+                  installing Fima.
+
+2. The following PEAR packages:
+   (See `horde/docs/INSTALL`_ for instructions on installing PEAR packages)
+
+   a. Image_Graph 0.7.2 [OPTIONAL]
+
+      Fima uses the Image_Graph package for creating graphical reports.
+
+3. SQL support.
+
+   Fima will store its data in an SQL database. Build PHP with whichever
+   SQL driver you require; see the Horde INSTALL_ file for details.
+
+
+Installing Fima
+===================
+
+Fima is written in PHP, and must be installed in a web-accessible
+directory. The precise location of this directory will differ from system to
+system. Conventionally, Fima is installed directly underneath Horde in the
+web server's document tree.
+
+Since Fima is written in PHP, there is no compilation necessary; simply
+expand the distribution where you want it to reside and rename the root
+directory of the distribution to whatever you wish to appear in the URL. For
+example, with the Apache web server's default document root of
+``/usr/local/apache/htdocs``, you would type::
+
+   cd /usr/local/apache/htdocs/horde
+   tar zxvf /path/to/fima-h3-x.y.z.tar.gz
+   mv fima-h3-x.y.z fima
+
+and would then find Fima at the URL::
+
+   http://your-server/horde/fima/
+
+
+Configuring Fima
+====================
+
+1. Configuring Horde for Fima
+
+   a. Register the application
+
+      In ``horde/config/registry.php``, find the ``applications['fima']``
+      stanza. The default settings here should be okay, but you can change
+      them if desired. If you have changed the location of Fima relative
+      to Horde, either in the URL, in the filesystem or both, you must update
+      the ``fileroot`` and ``webroot`` settings to their correct values.
+
+2. Creating the database tables
+
+   The specific steps to create Fima's database tables depend on which
+   database you've chosen to use.
+
+   First, look in ``scripts/sql/`` to see if a script already exists for your
+   database type. If so, you should be able to simply execute that script as
+   superuser in your database. (Note that executing the script as the "horde"
+   user will probably fail when granting privileges.)
+
+   If such a script does not exist, you'll need to build your own, using the
+   file ``fima.sql`` as a starting point.  If you need assistance in
+   creating database tables, you may wish to let us know on the Fima
+   mailing list.
+
+   You will also need to make sure that the "horde" user in your database has
+   table-creation privileges, so that the tables that `PEAR DB`_ uses to
+   provide portable sequences can be created.
+
+   .. _`PEAR DB`: http://pear.php.net/DB
+
+3. Configuring Fima
+
+   To configure Fima, change to the ``config/`` directory of the installed
+   distribution, and make copies of all of the configuration ``dist`` files
+   without the ``dist`` suffix::
+
+      cd config/
+      for foo in *.dist; do cp $foo `basename $foo .dist`; done
+
+   Or on Windows::
+
+      copy *.dist *.
+
+   Documentation on the format and purpose of those files can be found in each
+   file. You may edit these files if you wish to customize Fima's
+   appearance and behavior. With one exception (``foo.php``) the defaults will
+   be correct for most sites.
+
+   You must login to Horde as a Horde Administrator to finish the
+   configuration of Fima. Use the Horde ``Administration`` menu item to
+   get to the administration page, and then click on the ``Configuration``
+   icon to get the configuration page. Select ``Fima Name`` from the
+   selection list of applications. Fill in or change any configuration values
+   as needed. When done click on ``Generate Fima Name Configuration`` to
+   generate the ``conf.php`` file. If your web server doesn't have write
+   permissions to the Fima configuration directory or file, it will not be
+   able to write the file. In this case, go back to ``Configuration`` and
+   choose one of the other methods to create the configuration file
+   ``fima/config/conf.php``.
+
+   Note for international users: Fima uses GNU gettext to provide local
+   translations of text displayed by applications; the translations are found
+   in the ``po/`` directory. If a translation is not yet available for your
+   locale (and you wish to create one), see the ``horde/po/README`` file, or
+   if you're having trouble using a provided translation, please see the
+   `horde/docs/TRANSLATIONS`_ file for instructions.
+
+4. More instructions, upgrading, securing, etc.
+
+5. Testing Fima
+
+   Once you have configured Fima, bring up the included test page in your
+   Web browser to ensure that all necessary prerequisites have been met. See
+   the `horde/docs/INSTALL`_ document for further details on Horde test
+   scripts. If you installed Fima as described above, the URL to the test
+   page would be::
+
+      http://your-server/horde/fima/test.php
+
+   Test at least the following:
+
+   - Creating a new account
+   - Modifying an account
+   - Deleting an account
+   - Creating a new posting
+   - Modifying a posting
+   - Delete a posting
+
+
+Known Problems
+==============
+
+None yet.
+
+
+Obtaining Support
+=================
+
+If you encounter problems with Fima, help is available!
+
+The Horde Frequently Asked Questions List (FAQ), available on the Web at
+
+  http://www.horde.org/faq/
+
+The Horde Project runs a number of mailing lists, for individual applications
+and for issues relating to the project as a whole. Information, archives, and
+subscription information can be found at
+
+  http://www.horde.org/mail/
+
+Lastly, Horde developers, contributors and users may also be found on IRC,
+on the channel #horde on the Freenode Network (irc.freenode.net).
+
+Please keep in mind that Fima is free software written by volunteers.
+For information on reasonable support expectations, please read
+
+  http://www.horde.org/support.php
+
+Thanks for using Fima!
+
+The Fima team
+
+
+.. _README: ?f=README.html
+.. _`horde/docs/HACKING`: ../../horde/docs/?f=HACKING.html
+.. _`horde/docs/INSTALL`: ../../horde/docs/?f=INSTALL.html
+.. _`horde/docs/TRANSLATIONS`: ../../horde/docs/?f=TRANSLATIONS.html
diff --git a/fima/docs/RELEASE_NOTES b/fima/docs/RELEASE_NOTES
new file mode 100644 (file)
index 0000000..d280106
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Release focus. Possible values:
+ * 0 - N/A
+ * 1 - Initial freshmeat announcement
+ * 2 - Documentation
+ * 3 - Code cleanup
+ * 4 - Minor feature enhancements
+ * 5 - Major feature enhancements
+ * 6 - Minor bugfixes
+ * 7 - Major bugfixes
+ * 8 - Minor security fixes
+ * 9 - Major security fixes
+ */
+$this->notes['fm']['focus'] = 0;
+
+/* Mailing list release notes. */
+$this->notes['ml']['changes'] = <<<ML
+The Horde Team is pleased to announce the first release candidate 
+of the Fima Application version H3 (1.0).
+
+Fima is a double entry based ledger written in PHP and utilizing the Horde
+Application Framework.
+
+Barring any problems, this code will be released as Skeleton H3 (x.x).
+Testing is requested and comments are encouraged.
+Updated translations would also be great.
+
+ML;
+
+/* Freshmeat release notes, not more than 600 characters. */
+$this->notes['fm']['changes'] = <<<FM
+FM;
+
+$this->notes['name'] = 'Fima';
+$this->notes['fm']['project'] = 'fima';
+$this->notes['fm']['branch'] = 'Default';
diff --git a/fima/docs/TODO b/fima/docs/TODO
new file mode 100644 (file)
index 0000000..1b81392
--- /dev/null
@@ -0,0 +1,12 @@
+================================
+ Fima Development TODO List
+================================
+
+:Last update:   $Date: 2009/03/13 18:02:54 $
+:Revision:      $Revision: 1.1 $
+
+- fix search with umlaute
+- drill down in reports
+- shift all postings from an account or only selected
+- simplified entering of budget/forecast values analog to report Account Overview
+- after selecting asset account immediately show result
diff --git a/fima/index.php b/fima/index.php
new file mode 100644 (file)
index 0000000..9bc17c5
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+/**
+ * $Horde: fima/index.php,v 1.0 2008/04/25 17:59:00 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Thomas Trethan <thomas@trethan.net>
+ */
+
+@define('FIMA_BASE', dirname(__FILE__));
+$fima_configured = (is_readable(FIMA_BASE . '/config/conf.php') &&
+                    is_readable(FIMA_BASE . '/config/prefs.php'));
+
+if (!$fima_configured) {
+    require FIMA_BASE . '/../lib/Test.php';
+    Horde_Test::configFilesMissing('Fima', FIMA_BASE,
+                                   array('conf.php', 'prefs.php'));
+}
+
+require FIMA_BASE . '/postings.php';
diff --git a/fima/ledgers/create.php b/fima/ledgers/create.php
new file mode 100644 (file)
index 0000000..6c9386a
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * $Horde: fima/ledgers/create.php,v 1.0 2008/06/19 08:01:33 trt Exp $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('FIMA_BASE', dirname(dirname(__FILE__)));
+require_once FIMA_BASE . '/lib/base.php';
+require_once FIMA_BASE . '/lib/Forms/CreateLedger.php';
+
+// Exit if this isn't an authenticated user or if the user can't
+// create new task lists (default share is locked).
+if (!Auth::getAuth() || $prefs->isLocked('active_ledger')) {
+    header('Location: ' . Horde::applicationUrl('postings.php', true));
+    exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$form = new Fima_CreateLedgerForm($vars);
+
+// Execute if the form is valid.
+if ($form->validate($vars)) {
+    $result = $form->execute();
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push($result, 'horde.error');
+    } else {
+        $notification->push(sprintf(_("The ledger \"%s\" has been created."), $vars->get('name')), 'horde.success');
+    }
+
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+}
+
+$title = $form->getTitle();
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'create.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/ledgers/delete.php b/fima/ledgers/delete.php
new file mode 100644 (file)
index 0000000..9d89f53
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * $Horde: fima/ledgers/delete.php,v 1.0 2008/06/19 08:07:33 trt Exp $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('FIMA_BASE', dirname(dirname(__FILE__)));
+require_once FIMA_BASE . '/lib/base.php';
+require_once FIMA_BASE . '/lib/Forms/DeleteLedger.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+    header('Location: ' . Horde::applicationUrl('postings.php', true));
+    exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$ledger_id = $vars->get('l');
+if ($ledger_id == Auth::getAuth()) {
+    $notification->push(_("This ledger cannot be deleted."), 'horde.warning');
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+}
+
+$ledger = $fima_shares->getShare($ledger_id);
+if (is_a($ledger, 'PEAR_Error')) {
+    $notification->push($ledger, 'horde.error');
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+} elseif ($ledger->get('owner') != Auth::getAuth()) {
+    $notification->push(_("You are not allowed to delete this ledger."), 'horde.error');
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+}
+
+$form = new Fima_DeleteLedgerForm($vars, $ledger);
+
+// Execute if the form is valid (must pass with POST variables only).
+if ($form->validate(new Variables($_POST))) {
+    $result = $form->execute();
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push($result, 'horde.error');
+    } elseif ($result) {
+        $notification->push(sprintf(_("The ledger \"%s\" has been deleted."), $ledger->get('name')), 'horde.success');
+    }
+
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+}
+
+$title = $form->getTitle();
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'delete.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/ledgers/edit.php b/fima/ledgers/edit.php
new file mode 100644 (file)
index 0000000..1ddc22c
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * $Horde: fima/ledgers/edit.php,v 1.0 2008/06/19 08:04:33 trt Exp $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('FIMA_BASE', dirname(dirname(__FILE__)));
+require_once FIMA_BASE . '/lib/base.php';
+require_once FIMA_BASE . '/lib/Forms/EditLedger.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+    header('Location: ' . Horde::applicationUrl('postings.php', true));
+    exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$ledger = $fima_shares->getShare($vars->get('l'));
+if (is_a($ledger, 'PEAR_Error')) {
+    $notification->push($ledger, 'horde.error');
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+} elseif ($ledger->get('owner') != Auth::getAuth()) {
+    $notification->push(_("You are not allowed to change this ledger."), 'horde.error');
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+}
+$form = new Fima_EditLedgerForm($vars, $ledger);
+
+// Execute if the form is valid.
+if ($form->validate($vars)) {
+    $original_name = $ledger->get('name');
+    $result = $form->execute();
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push($result, 'horde.error');
+    } else {
+        if ($ledger->get('name') != $original_name) {
+            $notification->push(sprintf(_("The ledger \"%s\" has been renamed to \"%s\"."), $original_name, $ledger->get('name')), 'horde.success');
+        } else {
+            $notification->push(sprintf(_("The ledger \"%s\" has been saved."), $original_name), 'horde.success');
+        }
+    }
+
+    header('Location: ' . Horde::applicationUrl('ledgers/', true));
+    exit;
+}
+
+$vars->set('name', $ledger->get('name'));
+$vars->set('description', $ledger->get('desc'));
+$title = $form->getTitle();
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'edit.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/ledgers/index.php b/fima/ledgers/index.php
new file mode 100644 (file)
index 0000000..14848d7
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * $Horde: fima/ledgers/index.php,v 1.0 2008/06/18 18:41:05 trt Exp $
+ *
+ * Copyright 2001-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ */
+
+@define('FIMA_BASE', dirname(dirname(__FILE__)));
+require_once FIMA_BASE . '/lib/base.php';
+
+/* Exit if this isn't an authenticated user. */
+if (!Auth::getAuth()) {
+    require FIMA_BASE . '/postings.php';
+    exit;
+}
+
+$edit_url_base = Horde::applicationUrl('ledgers/edit.php');
+$perms_url_base = Horde::url($registry->get('webroot', 'horde') . '/services/shares/edit.php?app=fima', true);
+$delete_url_base = Horde::applicationUrl('ledgers/delete.php');
+
+// Get the shares owned by the current user, and figure out what we will
+// display the share name as to the user.
+$ledgers = Fima::listLedgers(true);
+$sorted_ledgers = array();
+foreach ($ledgers as $ledger) {
+    $sorted_ledgers[$ledger->getName()] = $ledger->get('name');
+}
+asort($sorted_ledgers);
+
+$browse_img = Horde::img('accounts.png', _("Ledger"), null, $registry->getImageDir('fima'));
+$edit_img = Horde::img('edit.png', _("Edit"), null, $registry->getImageDir('horde'));
+$perms_img = Horde::img('perms.png', _("Change Permissions"), null, $registry->getImageDir('horde'));
+$delete_img = Horde::img('delete.png', _("Delete"), null, $registry->getImageDir('horde'));
+
+Horde::addScriptFile('popup.js', 'horde', true);
+Horde::addScriptFile('tables.js', 'turba', true);
+$title = _("Manage Ledgers");
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+require FIMA_TEMPLATES . '/ledgers_list.php';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/lib/Block/summary.php b/fima/lib/Block/summary.php
new file mode 100644 (file)
index 0000000..dd9c046
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+
+$block_name = _("Finances Results");
+
+/**
+ * $Horde: fima/lib/Block/summary.php,v 1.0 2008/06/16 23:19:10 trt  Exp $
+ *
+ * @package Horde_Block
+ */
+class Horde_Block_fima_summary extends Horde_Block {
+
+    var $_app = 'fima';
+
+    function _title()
+    {
+        global $registry;
+
+        $label = !empty($this->_params['block_title'])
+            ? $this->_params['block_title']
+            : $registry->get('name');
+        return Horde::link(Horde::applicationUrl($registry->getInitialPage(), true))
+            . htmlspecialchars($label) . '</a>';
+    }
+
+    function _params()
+    {
+        require_once dirname(__FILE__) . '/../base.php';
+        $ledgers = array();
+        foreach (Fima::listLedgers() as $id => $ledger) {
+            $ledgers[$id] = $ledger->get('name');
+        }
+
+        return array('block_title' => array(
+                         'type' => 'text',
+                         'name' => _("Block title"),
+                         'default' => $GLOBALS['registry']->get('name')),
+                     'show_ledger' => array(
+                         'type' => 'enum',
+                         'name' => _("Show summary of this ledger"),
+                         'default' => Auth::getAuth(),
+                         'values' => $ledgers),
+                     'show_months' => array(
+                         'type' => 'enum',
+                         'name' => _("Number of months to display"),
+                         'default' => '3',
+                         'values' => array(
+                             '1' => '1',
+                             '2' => '2',
+                             '3' => '3',
+                             '4' => '4',
+                             '5' => '5',
+                             '6' => '6',
+                         )));
+    }
+
+    function _content()
+    {
+        global $registry, $prefs;
+        require_once dirname(__FILE__) . '/../base.php';
+
+        $now = time();
+        $html = '';
+        
+        /* Get account types and posting types. */
+        $accounttypes = Fima::getAccountTypes();
+        
+        /* Params. */
+        $showmonths = $this->_params['show_months'];
+        $datefmt = Fima::convertDateToPeriodFormat($GLOBALS['prefs']->getValue('date_format'));
+        $period_start = mktime(0, 0, 0, date('n') - $showmonths + 1, 1);
+        $period_end = mktime(0, 0, 0);
+        
+        /* Rows. */
+        $rows = array(FIMA_ACCOUNTTYPE_INCOME => $accounttypes[FIMA_ACCOUNTTYPE_INCOME],
+                      FIMA_ACCOUNTTYPE_EXPENSE => $accounttypes[FIMA_ACCOUNTTYPE_EXPENSE],
+                      '__result__' => _("Total Result"),
+                      '__resultasset__' => _("Asset Result"));
+        
+        /* Columns. */
+        $cols = array();
+        $coldummy = array();
+        
+        for ($period = $period_start; $period <= $period_end; $period = strtotime('+1 month', $period)) {
+            $colId = strftime('%Y%m', $period);
+            $cols[$colId] = strftime($datefmt, $period);
+            $coldummy[$colId] = 0;
+        }
+        
+        /* Initialize matrix. */
+        $data = array('__header__' => array('__header__' => ''));
+        foreach ($cols as $colId => $col) {
+            $data['__header__'][$colId] = $col;
+        }
+        foreach ($rows as $rowId => $row) {
+            $data[$rowId] = array('__header__' => $row) + $coldummy;
+        }
+        
+        /* Results. */
+        $filters = array();
+        $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET, '<>');
+        $filters[] = array('type', FIMA_POSTINGTYPE_ACTUAL);
+        $filters[] = array('date', (int)$period_start, '>=');
+        $filters[] = array('date', (int)$period_end, '<=');
+        $result = Fima::getResults(array('date_month', 'account_type'), $filters);
+        if (is_a($result, 'PEAR_Error')) {
+            return '<em>' . _("Error when retrieving results.") . '</em>';
+        }
+        foreach ($result as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $data[$rowId][$colId] = $value;
+                $data['__result__'][$colId] += $value;
+            }
+        }
+        
+        /* Asset Results. */
+        $filters = array();
+        $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET, '<>');
+        $filters[] = array('type', FIMA_POSTINGTYPE_ACTUAL);
+        $filters[] = array('date', (int)$period_start, '<');
+        $result = Fima::getResults(array('type'), $filters);
+        if (is_a($result, 'PEAR_Error')) {
+            return '<em>' . _("Error when retrieving results.") . '</em>';
+        }
+        $assetresult = 0;
+        foreach ($result as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $assetresult += $value;
+            }
+        }
+        foreach ($data['__resultasset__'] as $colId => $col) {
+            if (preg_match('/__header.*__/', $colId)) {
+                continue;
+            }
+            $assetresult += $data['__result__'][$colId];
+            $data['__resultasset__'][$colId] = $assetresult;
+        }
+        
+        /* Output. */
+        foreach ($data as $rowId => $row) {
+            $html .= '<tr class="' . (preg_match('/__result(.*)?__/', $rowId) ? 'result' : 'item') . '">';
+            foreach ($row as $colId => $value) {
+                if ($rowId === '__header__') {
+                    $html .= '<th class="item">' . htmlspecialchars($value) . '</th>';
+                } elseif ($colId === '__header__') {
+                    $html .= '<td>' . htmlspecialchars($value) . '</td>';
+                } else {
+                    $html .= '<td class="' . (($value < 0) ? 'negative' : 'positive') . ' amount">' . Fima::convertValueToAmount($value) . '</td>';
+                }
+            }
+            $html .= '</tr>';
+        }
+
+        if (empty($html)) {
+            return '<em>' . _("No results to display") . '</em>';
+        }
+
+        return '<table cellspacing="0" class="reportTable" width="100%">'
+            . $html . '</table>';
+    }
+
+}
diff --git a/fima/lib/Block/tree_menu.php b/fima/lib/Block/tree_menu.php
new file mode 100644 (file)
index 0000000..7325cbf
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+$block_name = _("Menu List");
+$block_type = 'tree';
+
+/**
+ * $Horde: fima/lib/Block/tree_menu.php,v 1.0 2008/06/03 00:11:13 trt Exp $
+ */
+class Horde_Block_fima_tree_menu extends Horde_Block {
+
+    var $_app = 'fima';
+
+    function _buildTree(&$tree, $indent = 0, $parent = null)
+    {
+        global $registry;
+
+        $menus = array(
+            array('add', _("Add Postings"), 'add.png', Util::addParameter(Horde::applicationUrl('postings.php'), 'actionID', 'add_postings')),
+            array('search', _("Search"), 'search.png', Horde::applicationUrl('search.php'), $registry->getImageDir('horde')),
+            array('accounts', _("Accounts"), 'accounts.png', Horde::applicationUrl('accounts.php')),
+            array('reports', _("Reports"), 'report.png', Horde::applicationUrl('report.php')),
+        );
+        
+        foreach ($menus as $menu) {
+            $tree->addNode($parent . $menu[0],
+                           $parent,
+                           $menu[1],
+                           $indent + 1,
+                           false,
+                           array('icon' => $menu[2],
+                                 'icondir' => isset($menu[4]) ? $menu[4] : $registry->getImageDir(),
+                                 'url' => $menu[3]));
+        }
+    }
+
+}
diff --git a/fima/lib/Driver.php b/fima/lib/Driver.php
new file mode 100644 (file)
index 0000000..0a18222
--- /dev/null
@@ -0,0 +1,421 @@
+<?php
+/**
+ * Fima_Driver:: defines an API for implementing storage backends for
+ * Fima.
+ *
+ * $Horde: fima/lib/Driver.php,v 1.1 2009/03/11 17:16:00 trt Exp $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Driver {
+
+    /**
+     * Array holding the current accounts. Each array entry is a hash
+     * describing an account. The array is indexed by accountId.
+     *
+     * @var array
+     */
+    var $_accounts = array();
+    
+    /**
+     * Array holding the current postings. Each array entry is a hash
+     * describing a posting. The array is indexed by postingId.
+     *
+     * @var array
+     */
+    var $_postings = array();
+
+    /**
+     * Integer containing the current total count of postings.
+     *
+     * @var integer
+     */
+    var $_postingsCount = 0;
+    
+    /**
+     * Amount containing the current total result of postings.
+     *
+     * @var float
+     */
+    var $_postingsResult = 0;
+    
+    /**
+     * String containing the current ledger.
+     *
+     * @var string
+     */
+    var $_ledger = '';
+
+    /**
+     * Hash containing connection parameters.
+     *
+     * @var array
+     */
+    var $_params = array();
+
+    /**
+     * Constructor - just store the $params in our newly-created
+     * object. All other work is done by initialize().
+     *
+     * @param array $params  Any parameters needed for this driver.
+     */
+    function Fima_Driver($params = array(), $errormsg = null)
+    {
+        $this->_params = $params;
+        if (is_null($errormsg)) {
+            $this->_errormsg = _("The Finances backend is not currently available.");
+        } else {
+            $this->_errormsg = $errormsg;
+        }
+    }
+
+    /**
+     * Returns the current driver's additional parameters.
+     *
+     * @return array  Hash containing the driver's additional parameters.
+     */
+    function getParams()
+    {
+        return $this->_params;
+    }
+
+    /**
+     * Lists accounts based on the given criteria. All accounts will be
+     * returned by default.
+     *
+     * @return array  Returns a list of the requested accounts.
+     */
+    function listAccounts()
+    {
+        return $this->_accounts;
+    }
+
+    /**
+     * Lists postings based on the given criteria. All postings will be
+     * returned by default.
+     *
+     * @return array  Returns a list of the requested postings.
+     */
+    function listPostings()
+    {
+        return $this->_postings;
+    }
+
+    /**
+     * Adds an account.
+     *
+     * @param string $number     The number of the account.
+     * @param string $type       The type of the account.
+     * @param string $name       The name (short) of the account.
+     * @param boolean $eo           Extraordinary account.
+     * @param string $desc       The description (long) of the account.
+     * @param boolean $closed    Close account.
+     *
+     * @return mixed             ID of the new account or PEAR_Error
+     */
+    function addAccount($number, $type, $name, $eo, $desc, $closed)
+    {
+        $accountId = $this->_addAccount($number, $type, $name, $eo, $desc, $closed);
+        if (is_a($accountId, 'PEAR_Error')) {
+            return $accountId;
+        }
+
+        /* Log the creation of this item in the history log. */
+        $history = &Horde_History::singleton();
+        $history->log('fima:' . $this->_ledger . ':' . $accountId, array('action' => 'add'), true);
+        
+        return $accountId;
+    }
+
+    /**
+     * Modifies an existing account.
+     *
+     * @param string $accountId  The account to modify.
+     * @param string $number     The number of the account.
+     * @param string $type       The type of the account.
+     * @param string $name       The name (short) of the task.
+     * @param boolean $eo           Extraordinary account.
+     * @param string $desc       The description (long) of the task.
+     * @param boolean $closed    Close account.
+     *
+     * return mixed              True or PEAR_Error
+     */
+    function modifyAccount($accountId, $number, $type, $name, $eo, $desc, $closed)
+    {
+        $modify = $this->_modifyAccount($accountId, $number, $type, $name, $eo, $desc, $closed);
+        if (is_a($modify, 'PEAR_Error')) {
+            return $modify;
+        }
+
+        /* Log the modification of this item in the history log. */
+        $account = $this->getAccount($accountId);
+        if (!is_a($account, 'PEAR_Error')) {
+            $history = &Horde_History::singleton();
+            $history->log('fima:' . $this->_ledger . ':' . $account['account_id'], array('action' => 'modify'), true);
+        }
+
+        return true;
+    }
+    
+    /**
+     * Deletes an account and deletes/shifts of subaccounts and postings.
+     *
+     * @param string $accountId     The account to delete.
+     * @param mixed $dsSubaccounts  True/false when deleting subaccounts,
+     *                                                                 accountId when shifting subaccounts
+     * @param mixed $dsPostings     True/false when deleting postings,
+     *                                                                 accountId when shifting postings
+     *
+     * @return mixed                True or PEAR_Error
+     */
+    function deleteAccount($accountId, $dsSubaccounts = false, $dsPostings = true)
+    {
+        /* Get the account's details for use later. */
+        $account = $this->getAccount($accountId);
+
+        $delete = $this->_deleteAccount($accountId, $dsSubaccounts, $dsPostings);
+        if (is_a($delete, 'PEAR_Error')) {
+            return $delete;
+        }
+
+        /* Log the deletion of this item in the history log. */
+        if (!is_a($account, 'PEAR_Error')) {
+            $history = &Horde_History::singleton();
+            $history->log('fima:' . $this->_ledger . ':' . $account['account_id'], array('action' => 'delete'), true);
+        }
+
+        return true;
+    }
+
+    /**
+     * Adds a posting.
+     *
+     * @param string $type     The posting type.
+     * @param integer $date    The posting date.
+     * @param string $asset    The ID of the asset account.
+     * @param string $account  The ID of the account.
+     * @param boolean $eo         Extraordinary posting.
+     * @param float $amount    The posting amount.
+     * @param string $desc     The posting description.
+     *
+     * @return mixed           ID of the new posting or PEAR_Error
+     */
+    function addPosting($type, $date, $asset, $account, $eo, $amount, $desc)
+    {
+        $postingId = $this->_addPosting($type, $date, $asset, $account, $eo, $amount, $desc);
+        if (is_a($postingId, 'PEAR_Error')) {
+            return $postingId;
+        }
+
+        /* Log the creation of this item in the history log. */
+        $history = &Horde_History::singleton();
+        $history->log('fima:' . $this->_ledger . ':' . $postingId, array('action' => 'add'), true);
+        
+        return $postingId;
+    }
+
+    /**
+     * Modifies an existing posting.
+     *
+     * @param string $postingId  The posting to modify.
+     * @param string $type       The posting type.
+     * @param integer $date      The posting date.
+     * @param string $asset      The ID of the asset account.
+     * @param string $account    The ID of the account.
+     * @param boolean $eo           Extraordinary posting.
+     * @param float $amount      The posting amount.
+     * @param string $desc       The posting description.
+     *
+     * @return mixed             True or PEAR_Error
+     */
+    function modifyPosting($postingId, $type, $date, $asset, $account, $eo, $amount, $desc)
+    {
+        $modify = $this->_modifyPosting($postingId, $type, $date, $asset, $account, $eo, $amount, $desc);
+        if (is_a($modify, 'PEAR_Error')) {
+            return $modify;
+        }
+
+        /* Log the modification of this item in the history log. */
+        $posting = $this->getPosting($postingId);
+        if (!is_a($posting, 'PEAR_Error')) {
+            $history = &Horde_History::singleton();
+            $history->log('fima:' . $this->_ledger . ':' . $posting['posting_id'], array('action' => 'modify'), true);
+        }
+
+        return true;
+    }
+    
+    /**
+     * Deletes a posting.
+     *
+     * @param string $postingId     The posting to delete.
+     *
+     * @return mixed                True or PEAR_Error
+     */
+    function deletePosting($postingId)
+    {
+        /* Get the posting's details for use later. */
+        $posting = $this->getPosting($postingId);
+
+        $delete = $this->_deletePosting($postingId);
+        if (is_a($delete, 'PEAR_Error')) {
+            return $delete;
+        }
+
+        /* Log the deletion of this item in the history log. */
+        if (!is_a($posting, 'PEAR_Error')) {
+            $history = &Horde_History::singleton();
+            $history->log('fima:' . $this->_ledger . ':' . $posting['posting_id'], array('action' => 'delete'), true);
+        }
+
+        return true;
+    }
+    
+    /**
+     * Shifts a posting.
+     *
+     * @param string $postingId  The posting to shift.
+     * @param string $type              The posting type shifting to.
+     * @param string $asset      The ID of the asset account.
+     * @param string $account    The ID of the account.
+     *
+     * @return mixed             True or PEAR_Error
+     */
+    function shiftPosting($postingId, $type, $asset, $account)
+    {
+        /* Get the posting's details for use later. */
+        $posting = $this->getPosting($postingId);
+        $shift = $this->_shiftPosting($postingId, $type, $asset, $account);
+        if (is_a($shift, 'PEAR_Error')) {
+            return $shift;
+        }
+
+        /* Log the shifting of this item in the history log. */
+        if (!is_a($posting, 'PEAR_Error')) {
+            $history = &Horde_History::singleton();
+            $history->log('fima:' . $this->_ledger . ':' . $posting['posting_id'], array('action' => 'shift'), true);
+        }
+
+        return true;
+    }
+
+    /**
+     * Deletes all postings and accounts.
+     *
+     * @param mixed $accounts  boolean or account_type
+     * @param mixed $accounts  boolean or posting_type.
+     *
+     * @return mixed             True or PEAR_Error
+     */
+    function deleteAll($accounts = true, $postings = true)
+    {
+        $delete = $this->_deleteAll($accounts, $postings);
+        if (is_a($delete, 'PEAR_Error')) {
+            return $delete;
+        }
+
+        /* Log the deletion of this item in the history log. */
+        $history = &Horde_History::singleton();
+        $history->log('fima:' . $this->_ledger . ':all', array('action' => 'delete'), true);
+
+        return true;
+    }
+    
+    /**
+     * Attempts to return a concrete Fima_Driver instance based on $driver.
+     *
+     * @param string    $ledger     The name of the ledger to load.
+     *
+     * @param string    $driver     The type of the concrete Fima_Driver subclass
+     *                              to return.  The class name is based on the
+     *                              storage driver ($driver).  The code is
+     *                              dynamically included.
+     *
+     * @param array     $params     (optional) A hash containing any additional
+     *                              configuration or connection parameters a
+     *                              subclass might need.
+     *
+     * @return mixed    The newly created concrete Fima_Driver instance, or
+     *                                 false on an error.
+     */
+    function &factory($ledger = '', $driver = null, $params = null)
+    {
+        if ($driver === null) {
+            $driver = $GLOBALS['conf']['storage']['driver'];
+        }
+        $driver = basename($driver);
+
+        if (is_null($params)) {
+            $params = Horde::getDriverConfig('storage', $driver);
+        }
+
+        require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
+        $class = 'Fima_Driver_' . $driver;
+        if (class_exists($class)) {
+            $fima = &new $class($ledger, $params);
+            $result = $fima->initialize();
+            if (is_a($result, 'PEAR_Error')) {
+                $fima =& new Fima_Driver($params, sprintf(_("The Finances backend is not currently available: %s"), $result->getMessage()));
+            }
+        } else {
+            $fima =& new Fima_Driver($params, sprintf(_("Unable to load the definition of %s."), $class));
+        }
+        
+        return $fima;
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Fima_Driver
+     * instance based on $driver. It will only create a new instance
+     * if no Fima_Driver instance with the same parameters currently
+     * exists.
+     *
+     * This should be used if multiple storage sources are required.
+     *
+     * This method must be invoked as: $var = &Fima_Driver::singleton()
+     *
+     * @param string    $ledger     The name of the ledger to load.
+     *
+     * @param string    $driver     The type of concrete Fima_Driver subclass
+     *                              to return.  The is based on the storage
+     *                              driver ($driver).  The code is dynamically
+     *                              included.
+     *
+     * @param array     $params     (optional) A hash containing any additional
+     *                              configuration or connection parameters a
+     *                              subclass might need.
+     *
+     * @return mixed    The created concrete Fima_Driver instance, or false
+     *                  on error.
+     */
+    function &singleton($ledger = '', $driver = null, $params = null)
+    {
+        static $instances;
+
+        if (is_null($driver)) {
+            $driver = $GLOBALS['conf']['storage']['driver'];
+        }
+
+        if (is_null($params)) {
+            $params = Horde::getDriverConfig('storage', $driver);
+        }
+
+        if (!isset($instances)) {
+            $instances = array();
+        }
+
+        $signature = serialize(array($ledger, $driver, $params));
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = &Fima_Driver::factory($ledger, $driver, $params);
+        }
+
+        return $instances[$signature];
+    }
+
+}
diff --git a/fima/lib/Driver/sql.php b/fima/lib/Driver/sql.php
new file mode 100644 (file)
index 0000000..a46a07d
--- /dev/null
@@ -0,0 +1,1112 @@
+<?php
+/**
+ * Fima storage implementation for PHP's PEAR database abstraction layer.
+ *
+ * Required values for $params:<pre>
+ *      'phptype'       The database type (e.g. 'pgsql', 'mysql', etc.).
+ *      'table'         The name of the foo table in 'database'.
+ *      'charset'       The database's internal charset.</pre>
+ *
+ * Required by some database implementations:<pre>
+ *      'database'      The name of the database.
+ *      'hostspec'      The hostname of the database server.
+ *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
+ *      'username'      The username with which to connect to the database.
+ *      'password'      The password associated with 'username'.
+ *      'options'       Additional options to pass to the database.
+ *      'tty'           The TTY on which to connect to the database.
+ *      'port'          The port on which to connect to the database.</pre>
+ *
+ * The table structure can be created by the scripts/sql/fima.sql
+ * script.
+ *
+ * $Horde: fima/lib/Driver/sql.php,v 1.1 2009/03/11 17:19:00 trt Exp $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Driver_sql extends Fima_Driver {
+
+    /**
+     * Handle for the current database connection.
+     *
+     * @var DB
+     */
+    var $_db;
+
+    /**
+     * Constructs a new SQL storage object.
+     *
+     * @param string $ledger  The ledger to load.
+     * @param array $params   A hash containing connection parameters.
+     */
+    function Fima_Driver_sql($ledger, $params = array())
+    {
+        $this->_ledger = $ledger;
+        $this->_params = $params;
+    }
+
+    /**
+     * Retrieves accounts from the database.
+     *
+     * @param array $filters  Any filters for restricting the retrieved accounts.
+     *
+     * @return mixed  True on success, PEAR_Error on failure.
+     */
+    function retrieveAccounts($filters = array())
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT * FROM %s WHERE account_owner = ?', $this->_params['table_accounts']);
+        $values = array($this->_ledger);
+
+        /* Add filters. */
+        $this->_addFilters($filters, $query, $values, 'account_');
+
+        /* Sorting. */
+        $query .= ' ORDER BY account_number ASC';
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::retrieveAccounts(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $this->_accounts = array();
+        $result = $this->_db->query($query, $values);
+
+        if (isset($result) && !is_a($result, 'PEAR_Error')) {
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            if (is_a($row, 'PEAR_Error')) {
+                return $row;
+            }
+
+            /* Store the retrieved values in the accounts variable. */
+            $this->_accounts = array();
+            while ($row && !is_a($row, 'PEAR_Error')) {
+                /* Add this new account to the $_account list. */
+                $this->_accounts[$row['account_id']] = $this->_buildAccount($row);
+
+                /* Advance to the new row in the result set. */
+                $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            }
+            $result->free();
+        } else {
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Retrieves one account from the database.
+     *
+     * @param string $accountId  The ID of the account to retrieve.
+     *
+     * @return array  The array of account attributes.
+     */
+    function getAccount($accountId)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT * FROM %s WHERE account_owner = ? AND account_id = ?',
+                         $this->_params['table_accounts']);
+        $values = array($this->_ledger, $accountId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::getAccount(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if (is_a($row, 'PEAR_Error')) {
+            return $row;
+        }
+        if ($row === null) {
+            return PEAR::raiseError(_("Not found"));
+        }
+
+        /* Decode and return the account. */
+        return $this->_buildAccount($row);
+    }
+
+    /**
+     * Retrieves one account from the database by number.
+     *
+     * @param string $number  The number of the account to retrieve.
+     *
+     * @return array  The array of account attributes.
+     */
+    function getAccountByNumber($number)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT * FROM %s WHERE account_owner = ? AND account_number = ?',
+                         $this->_params['table_accounts']);
+        $values = array($this->_ledger, sprintf('%\'04s', $number));
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::getAccountByNumber(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if (is_a($row, 'PEAR_Error')) {
+            return $row;
+        }
+        if ($row === null) {
+            return PEAR::raiseError(_("Not found"));
+        }
+
+        /* Decode and return the account. */
+        return $this->_buildAccount($row);
+    }
+
+    /**
+     * Retrieves postings from the database.
+     *
+     * @param array $filters  Any filters for restricting the retrieved postings.
+     * @param array $sorting  Sort order of retrieved postings.
+     * @param array $limit       Limit of the retrieved postings, array(page, postings/page).
+     *
+     * @return mixed  True on success, PEAR_Error on failure.
+     */
+    function retrievePostings($filters = array(), $sorting = array(), $limit = array())
+    {
+        /* Build the SQL query filter. */
+        $queryfilter = ' WHERE posting_owner = ?';
+        $values = array($this->_ledger);
+
+        /* Add filters. */
+        $this->_addFilters($filters, $queryfilter, $values, 'posting_');
+
+        $query = sprintf('SELECT count(p.posting_id) posting_count, SUM(p.posting_amount) posting_result FROM %s p',
+                         $this->_params['table_postings']);
+        $query .= $queryfilter;
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::retrievePostings(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $result = $this->_db->query($query, $values);
+        if (isset($result) && !is_a($result, 'PEAR_Error')) {
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            if (is_a($row, 'PEAR_Error')) {
+                return $row;
+            }
+            $this->_postingsCount = (int)$row['posting_count'];
+            $this->_postingsResult = $row['posting_result'];
+            $result->free();
+
+            // correct result when account is an asset account too
+            if ($this->_postingsCount > 0) {
+                $query = sprintf('SELECT SUM(p.posting_amount) posting_result ' .
+                                 'FROM %s p JOIN %s a ON a.account_id = p.posting_account ' . 
+                                 $queryfilter . ' AND a.account_type = ?',
+                                 $this->_params['table_postings'], $this->_params['table_accounts']);
+                $values2 = $values;
+                $values2[] = FIMA_ACCOUNTTYPE_ASSET;
+                $result = $this->_db->query($query, $values2);
+                if (isset($result) && !is_a($result, 'PEAR_Error')) {
+                    $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+                    if (is_a($row, 'PEAR_Error')) {
+                        return $row;
+                    }
+                }
+                $this->_postingsResult -= $row['posting_result'];
+                $result->free();
+            }
+        } else {
+            return $result;
+        }
+
+        /* Fetch the postings if necessary. */
+        $this->_postings = array();
+        if ($this->_postingsCount == 0) {
+            return true;
+        }
+
+        $query = sprintf('SELECT p.*, asset.account_number posting_asset_number, account.account_number posting_account_number ' .
+                         'FROM %s p LEFT OUTER JOIN %s asset ON p.posting_asset = asset.account_id LEFT OUTER JOIN %s account ON p.posting_account = account.account_id',
+                         $this->_params['table_postings'], $this->_params['table_accounts'], $this->_params['table_accounts']);
+        $query .= $queryfilter;
+
+        /* Sorting. */
+        if (!is_array($sorting)) {
+            $sorting = array($sorting);
+        }
+        if (count($sorting) == 0) {
+            $sorting = array('posting_date ASC');
+        }
+        $query .= ' ORDER BY ' . implode(', ', $sorting);
+        
+        /* Limit. */
+        if (count($limit) > 0) {
+            if ($limit[0] < 0) {
+                $limit[0] += ceil($this->_postingsCount / $limit[1]) + 1;
+            }
+            $limit[0] = ($limit[0] - 1) * $limit[1];
+            
+            $query .= ' LIMIT ' . $limit[0];
+            if (isset($limit[1])) {
+                $query .= ', ' . $limit[1];
+            }
+        }
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::retrievePostings(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $result = $this->_db->query($query, $values);
+        if (isset($result) && !is_a($result, 'PEAR_Error')) {
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            if (is_a($row, 'PEAR_Error')) {
+                return $row;
+            }
+
+            /* Store the retrieved values in the accounts variable. */
+            while ($row && !is_a($row, 'PEAR_Error')) {
+                /* Add this new posting to the $_posting list. */
+                $this->_postings[$row['posting_id']] = $this->_buildPosting($row);
+
+                /* Advance to the new row in the result set. */
+                $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            }
+            $result->free();
+        } else {
+            return $result;
+        }
+        
+        return true;
+    }
+
+    /**
+     * Retrieves one posting from the database.
+     *
+     * @param string $postingId  The ID of the posting to retrieve.
+     *
+     * @return array  The array of posting attributes.
+     */
+    function getPosting($postingId)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT * FROM %s WHERE posting_owner = ? AND posting_id = ?',
+                         $this->_params['table_postings']);
+        $values = array($this->_ledger, $postingId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::getPosting(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $result = $this->_db->query($query, $values);
+
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if (is_a($row, 'PEAR_Error')) {
+            return $row;
+        }
+        if ($row === null) {
+            return PEAR::raiseError(_("Not found"));
+        }
+
+        /* Decode and return the posting. */
+        return $this->_buildPosting($row);
+    }
+
+    /**
+     * Get grouped results.
+     *
+     * @param array $groups     Fields to group.
+     * @param boolean $filters  Filters for postings.
+     *
+     * @return array            A matrix of the grouped results.
+     */
+    function getResults($groups, $filters = array()) {
+        $matrix = array();
+        
+        /* Fix grouping. */
+        if (!is_array($groups)) {
+            $groups = array($groups);
+        }
+        if (!isset($groups[1])) {
+            $groups[1] = 'owner';
+        }
+        foreach ($groups as $groupId => $group) {
+            switch($group) {
+            case 'date_month':     $groups[$groupId] = 'FROM_UNIXTIME(posting_date, \'%Y%m\')'; break;
+            case 'date_year':      $groups[$groupId] = 'FROM_UNIXTIME(posting_date, \'%Y\')'; break;
+            case 'asset_number':   $groups[$groupId] = 'asset.account_number'; break;
+            case 'asset_parent':   $groups[$groupId] = 'CONCAT(LEFT(asset.account_number, 2), \'00\')'; break;
+            case 'asset_type':     $groups[$groupId] = 'asset.account_type'; break;
+            case 'account_number': $groups[$groupId] = 'account.account_number'; break;
+            case 'account_parent': $groups[$groupId] = 'CONCAT(LEFT(account.account_number, 2), \'00\')'; break;
+            case 'account_type':   $groups[$groupId] = 'account.account_type'; break;
+            default:               $groups[$groupId] = 'posting_'.$group; break;
+            }
+        }
+        
+        /* Build the SQL query filter. */
+        $query = sprintf('SELECT %s x, %s y, sum(posting_amount) result ' .
+                         'FROM %s p LEFT OUTER JOIN %s asset ON p.posting_asset = asset.account_id AND p.posting_owner = asset.account_owner LEFT OUTER JOIN %s account ON p.posting_account = account.account_id AND p.posting_owner = account.account_owner ' .
+                         'WHERE posting_owner = ?',
+                         $groups[0], $groups[1], $this->_params['table_postings'], $this->_params['table_accounts'], $this->_params['table_accounts']);
+        $values = array($this->_ledger);
+
+        /* Add filters. */
+        foreach ($filters as $filterId => $filter) {
+            switch($filter[0]) {
+            case 'date_month':     $filters[$filterId][0] = 'FROM_UNIXTIME(posting_date, \'%Y%m\')'; break;
+            case 'date_year':      $filters[$filterId][0] = 'FROM_UNIXTIME(posting_date, \'%Y%m\')'; break;
+            case 'asset_number':   $filters[$filterId][0] = 'asset.account_number'; break;
+            case 'asset_parent':   $filters[$filterId][0] = 'LEFT(asset.account_number, 2)'; break;
+            case 'asset_type':     $filters[$filterId][0] = 'asset.account_type'; break;
+            case 'account_number': $filters[$filterId][0] = 'account.account_number'; break;
+            case 'account_parent': $filters[$filterId][0] = 'LEFT(account.account_number, 2)'; break;
+            case 'account_type':   $filters[$filterId][0] = 'account.account_type'; break;
+            default:               $filters[$filterId][0] = 'posting_'.$filter[0]; break;
+            }
+        }
+        $this->_addFilters($filters, $query, $values);
+        
+        /* Add grouping. */
+        $query .= ' GROUP BY ' . implode(', ', $groups);
+        
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::getResults(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $result = $this->_db->query($query, $values);
+        if (isset($result) && !is_a($result, 'PEAR_Error')) {
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            if (is_a($row, 'PEAR_Error')) {
+                return $row;
+            }
+
+            /* Store the retrieved values in the accounts variable. */
+            while ($row && !is_a($row, 'PEAR_Error')) {
+                /* Add this new posting to the $_posting list. */
+                if (!isset($matrix[$row['y']])) {
+                    $matrix[$row['y']] = array();
+                }
+                $matrix[$row['y']][$row['x']] = $row['result'];
+
+                /* Advance to the new row in the result set. */
+                $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            }
+            $result->free();
+        }
+        
+        return $matrix;
+    }
+
+    /**
+     * Get the results of all asset accounts.
+     *
+     * @param string $postingtype  Type of postings.
+     * @param boolean $perdate     Date of asset results.
+     *
+     * @return array  Array of asset accounts and results
+     */
+    function getAssetResults($postingtype, $perdate = null)
+    {
+        $perdate = ($perdate === null) ? mktime() : (int)$perdate;
+        
+        /* Build the SQL query. */
+        $query = sprintf('SELECT account_id, SUM(account_result) account_result FROM ( ' .
+                         ' SELECT a1.account_id, SUM(p1.posting_amount) account_result ' .
+                         ' FROM %s a1 LEFT OUTER JOIN %s p1 ON a1.account_id = p1.posting_asset AND p1.posting_owner = ? AND p1.posting_type = ? ' .
+                         ' WHERE a1.account_owner = ? AND a1.account_type = ? and p1.posting_date <= ?' .
+                         ' GROUP BY a1.account_id ' .
+                         '  UNION ' .
+                         ' SELECT a2.account_id, SUM(p2.posting_amount) * -1 account_result ' .
+                         ' FROM %s a2 LEFT OUTER JOIN %s p2 ON a2.account_id = p2.posting_account AND p2.posting_owner = ? AND p2.posting_type = ? ' .
+                         ' WHERE a2.account_owner = ? AND a2.account_type = ? and p2.posting_date <= ?' .
+                         ' GROUP BY a2.account_id ' .
+                         ') x ' .
+                         'GROUP BY account_id ',
+                         $this->_params['table_accounts'], $this->_params['table_postings'],
+                         $this->_params['table_accounts'], $this->_params['table_postings']);
+        $values = array($this->_ledger, $postingtype, $this->_ledger, FIMA_ACCOUNTTYPE_ASSET, $perdate,
+                        $this->_ledger, $postingtype, $this->_ledger, FIMA_ACCOUNTTYPE_ASSET, $perdate);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::getAssetResults(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Execute the query. */
+        $assetresults = array();
+        $result = $this->_db->query($query, $values);
+        if (isset($result) && !is_a($result, 'PEAR_Error')) {
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            if (is_a($row, 'PEAR_Error')) {
+                return $row;
+            }
+
+            /* Store the retrieved values in the accounts variable. */
+            while ($row && !is_a($row, 'PEAR_Error')) {
+                /* Add this new posting to the $_posting list. */
+                $assetresults[] = $row;
+
+                /* Advance to the new row in the result set. */
+                $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+            }
+            $result->free();
+        } else {
+            return $result;
+        }
+
+        return $assetresults;
+    }
+
+    /**
+     * Build an account.
+     *
+     * @param array $row          Datasbase row holding account attributes.
+     * @param boolean $getparent  Also get parent account.
+     *
+     * @return array  The array of account attributes.
+     */
+    function _buildAccount($row, $getparent = true)
+    {
+        $parent = null;
+        if ($getparent) {
+            if (($parent_number = Fima::getAccountParent($row['account_number'])) !== null) {
+                if (isset($this->_accounts[$parent_number])) {
+                    $parent = $this->_accounts[$parent_number];
+                } else {
+                    $parent = $this->getAccountByNumber($parent_number);
+                    if (is_a($parent, 'PEAR_Error')) {
+                        $parent = null;
+                    }
+                }
+            }
+        }
+        
+        /* Create a new account based on $row's values. */
+        return array('account_id' => $row['account_id'],
+                     'owner' => $row['account_owner'],
+                     'number' => sprintf('%\'04d', $row['account_number']),
+                     'type' => $row['account_type'],
+                     'name' => String::convertCharset($row['account_name'], $this->_params['charset']),
+                     'eo' => $row['account_eo'],
+                     'desc' => String::convertCharset($row['account_desc'], $this->_params['charset']),
+                     'closed' => $row['account_closed'],
+                     'label' => trim($row['account_number'] . ' ' .
+                                (($parent === null) ? '' : $parent['name'] . ' - ') . 
+                                String::convertCharset($row['account_name'], $this->_params['charset'])),
+                     'parent_id' => ($parent === null) ? null : $parent['account_id'],
+                     'parent_number' => ($parent === null) ? '' : $parent['number'],
+                     'parent_name' => ($parent === null) ? '' : $parent['name']);
+    }
+    
+    /**
+     * Adds an account to the backend storage.
+     *
+     * @param string $number     The number of the account.
+     * @param string $type       The type of the account.
+     * @param string $name       The name (short) of the account.
+     * @param boolean $eo           Extraordinary account.
+     * @param string $desc       The description (long) of the account.
+     * @param boolean $closed    Close account.
+     *
+     * @return mixed             ID of the new account or PEAR_Error
+     */
+    function _addAccount($number, $type, $name, $eo, $desc, $closed)
+    {
+        $accountId = md5(uniqid(mt_rand(), true));
+
+        $query = sprintf(
+            'INSERT INTO %s (account_id, account_owner, account_number, account_type, ' .
+            'account_name, account_eo, account_desc, account_closed) ' .
+            'VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
+            $this->_params['table_accounts']);
+        $values = array($accountId,
+                        $this->_ledger,
+                        sprintf('%\'04d', $number),
+                        $type,
+                        String::convertCharset($name, NLS::getCharset(), $this->_params['charset']),
+                        (int)(bool)$eo,
+                        String::convertCharset($desc, NLS::getCharset(), $this->_params['charset']),
+                        (int)(bool)$closed);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_addAccount(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the insertion query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return $accountId;
+    }
+
+    /**
+     * Modifies an existing account.
+     *
+     * @param string $accountId  The account to modify.
+     * @param string $number     The number of the account.
+     * @param string $type       The type of the account.
+     * @param string $name       The name (short) of the account.
+     * @param boolean $eo           Extraordinary account.
+     * @param string $desc       The description (long) of the account.
+     * @param boolean $closed    Close account.
+     *
+     * @return mixed             True or PEAR_Error
+     */
+    function _modifyAccount($accountId, $number, $type, $name, $eo, $desc, $closed)
+    {
+        $query = sprintf('UPDATE %s SET' .
+                         ' account_number = ?, ' .
+                         ' account_type = ?, ' .
+                         ' account_name = ?, ' .
+                         ' account_eo = ?, ' .
+                         ' account_desc = ?, ' .
+                         ' account_closed = ? ' .
+                         'WHERE account_owner = ? AND account_id = ?',
+                         $this->_params['table_accounts']);
+        $values = array(sprintf('%\'04d', $number),
+                        $type,
+                        String::convertCharset($name, NLS::getCharset(), $this->_params['charset']),
+                        (int)(bool)$eo,
+                        String::convertCharset($desc, NLS::getCharset(), $this->_params['charset']),
+                        (int)(bool)$closed,
+                        $this->_ledger,
+                        $accountId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_modifyAccount(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the update query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return true;
+    }
+    
+    /**
+     * Deletes an account from the backend.
+     *
+     * @param string $accountId     The account to delete.
+     * @param mixed $dsSubaccounts  True/false when deleting subaccounts,
+     *                                                                 accountId when shifting subaccounts
+     * @param mixed $dsPostings     True/false when deleting postings,
+     *                                                                 accountId when shifting postings
+     *
+     * @return mixed                True or PEAR_Error
+     */
+    function _deleteAccount($accountId, $dsSubaccounts = false, $dsPostings = true)
+    {
+        /* Get the account's details for use later. */
+        $account = $this->getAccount($accountId);
+
+        /* Handle subaccounts. */
+        if ($dsSubaccounts !== false) {
+            /* Delete subaccounts. */
+            $parent = (int)($account['number'] / 100) . '%';
+            $this->retrieveAccounts(array(array('number', $parent, 'LIKE'),
+                                          array('number', (string)$account['number'], '!=')));
+
+            foreach ($this->_accounts as $subaccountId => $subaccount) {
+                $delete = $this->_deleteAccount($subaccountId, false, $dsSubaccounts);
+                if (is_a($delete, 'PEAR_Error')) {
+                    return $delete;
+                }
+            }
+        }     
+
+        /* Handle postings. */
+        if ($dsPostings !== false) {
+            if ($dsPostings === true) {
+                /* Delete account postings. */
+                $query = sprintf('DELETE FROM %s WHERE posting_owner = ? AND (posting_asset = ? OR posting_account = ?)',
+                                 $this->_params['table_postings']);
+                $values = array($this->_ledger, $accountId, $accountId);
+                    
+                /* Log the query at a DEBUG log level. */
+                Horde::logMessage(sprintf('Fima_Driver_sql::_deleteAccount(): %s', $query),
+                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+                /* Attempt the delete query. */
+                $result = $this->_db->query($query, $values);
+                if (is_a($result, 'PEAR_Error')) {
+                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                    return $result;
+                }
+            } else {
+                /* Shift account postings. */
+                $shift = $this->_shiftPostings($accountId, $dsPostings);
+                if (is_a($shift, 'PEAR_Error')) {
+                    return $shift;
+                }
+            }
+        }
+
+        /* Delete account. */
+        $query = sprintf('DELETE FROM %s WHERE account_owner = ? AND account_id = ?',
+                         $this->_params['table_accounts']);
+        $values = array($this->_ledger, $accountId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_deleteAccount(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the delete query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Build a posting.
+     *
+     * @param array $row  Datasbase row holding posting attributes;
+     *
+     * @return array  The array of posting attributes.
+     */
+    function _buildPosting($row)
+    {
+        /* Create a new account based on $row's values. */
+        return array('posting_id' => $row['posting_id'],
+                     'owner' => $row['posting_owner'],
+                     'type' => $row['posting_type'],
+                     'date' => $row['posting_date'],
+                     'asset' => $row['posting_asset'],
+                     'account' => $row['posting_account'],
+                     'desc' => String::convertCharset($row['posting_desc'], $this->_params['charset']),
+                     'amount' => $row['posting_amount'],
+                     'eo' => (int)(bool)$row['posting_eo']);
+    }
+    
+    /**
+     * Adds a posting to the backend storage.
+     *
+     * @param string $type     The posting type.
+     * @param integer $date    The posting date.
+     * @param string $asset    The ID of the asset account.
+     * @param string $account  The ID of the account.
+     * @param boolean $eo         Extraordinary posting.
+     * @param float $amount    The posting amount.
+     * @param string $desc     The posting description.
+     *
+     * @return mixed           ID of the new posting or PEAR_Error
+     */
+    function _addPosting($type, $date, $asset, $account, $eo, $amount, $desc)
+    {
+        $postingId = md5(uniqid(mt_rand(), true));
+
+        $query = sprintf(
+            'INSERT INTO %s (posting_id, posting_owner, posting_type, posting_date, ' .
+            'posting_asset, posting_account, posting_eo, posting_amount, posting_desc) ' .
+            'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
+            $this->_params['table_postings']);
+        $values = array($postingId,
+                        $this->_ledger,
+                        $type,
+                        (int)$date,
+                        $asset,
+                        $account,
+                        (int)(bool)$eo,
+                        (float)$amount,
+                        String::convertCharset($desc, NLS::getCharset(), $this->_params['charset']));
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_addPosting(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the insertion query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return $postingId;
+    }
+
+    /**
+     * Modifies an existing posting.
+     *
+     * @param string $postingId  The posting to modify.
+     * @param string $type       The posting type.
+     * @param integer $date      The posting date.
+     * @param string $asset      The ID of the asset account.
+     * @param string $account    The ID of the account.
+     * @param boolean $eo           Extraordinary posting.
+     * @param float $amount      The posting amount.
+     * @param string $desc       The posting description.
+     *
+     * @return mixed             True or PEAR_Error
+     */
+    function _modifyPosting($postingId, $type, $date, $asset, $account, $eo, $amount, $desc)
+    {
+        $query = sprintf('UPDATE %s SET' .
+                         ' posting_type = ?, ' .
+                         ' posting_date = ?, ' .
+                         ' posting_asset = ?, ' .
+                         ' posting_account = ?, ' .
+                         ' posting_eo = ?, ' .
+                         ' posting_amount = ?, ' .
+                         ' posting_desc = ? ' .
+                         'WHERE posting_owner = ? AND posting_id = ?',
+                         $this->_params['table_postings']);
+        $values = array($type,
+                        (int)$date,
+                        $asset,
+                        $account,
+                        (int)(bool)$eo,
+                        (float)$amount,
+                        String::convertCharset($desc, NLS::getCharset(), $this->_params['charset']),
+                        $this->_ledger,
+                        $postingId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_modifyPosting(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the update query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return true;
+    }
+    
+    /**
+     * Deletes a posting from the backend.
+     *
+     * @param string $postingId     The posting to delete.
+     *
+     * @return mixed                True or PEAR_Error
+     */
+    function _deletePosting($postingId)
+    {
+        /* Get the task's details for use later. */
+        $posting = $this->getPosting($postingId);
+
+        $query = sprintf('DELETE FROM %s WHERE posting_owner = ? AND posting_id = ?',
+                         $this->_params['table_postings']);
+        $values = array($this->_ledger, $postingId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_deletePosting(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the delete query. */
+        $result = $this->_db->query($query, $values);
+
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Shift an existing posting.
+     *
+     * @param string $postingId  The posting to shift.
+     * @param string $type              The posting type shifting to.
+     * @param string $asset      The ID of the asset account.
+     * @param string $account    The ID of the account.
+     *
+     * @return mixed             True or PEAR_Error
+     */
+    function _shiftPosting($postingId, $type, $asset, $account)
+    {
+        if (!$type && !$asset && !$account) {
+            return true;
+        }
+
+        $query = sprintf('UPDATE %s SET' .
+                         ($type ? ' posting_type = ?, ' : '') .
+                         ($asset ? ' posting_asset = ?, ' : '') .
+                         ($account ? ' posting_account = ?, ' : '').
+                         ' posting_eo = posting_eo ' .
+                         'WHERE posting_owner = ? AND posting_id = ?',
+                         $this->_params['table_postings']);
+        $values = array();
+        if ($type)    { $values[] = $type; }
+        if ($asset)   { $values[] = $asset; }
+        if ($account) { $values[] = $account; }
+        $values[] = $this->_ledger;
+        $values[] = $postingId;
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Fima_Driver_sql::_shiftPosting(): %s', $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the update query. */
+        $result = $this->_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        return true;
+    }
+    
+    /**
+     * Shift postings in the backend.
+     *
+     * @param mixed $accountIdFrom  The account(s) to shift postings from.
+     * @param string $accountIdTo   The account to shift postings to.
+     *
+     * @return mixed                True or PEAR_Error
+     */
+    function _shiftPostings($accountIdFrom, $accountIdTo)
+    {
+        if (!is_array($accountIdFrom)) {
+            $accountIdFrom = array($accountIdFrom);
+        }
+        
+        foreach ($accountIdFrom as $key => $value) {
+            $accountIdFrom[$key] = $this->_db->quoteSmart($value);
+        }
+
+        $fields = array('posting_asset', 'posting_account');
+        foreach ($fields as $field) {
+            $query = sprintf('UPDATE %s SET' .
+                             ' %s = ? ' .
+                             'WHERE posting_owner = ? AND %s IN (!)',
+                             $this->_params['table_postings'], $field, $field);
+            $values = array($accountIdTo, $this->_ledger, implode(',', $accountIdFrom));
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Fima_Driver_sql::_shiftPostings(): %s', $query),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            /* Attempt the update query. */
+            $result = $this->_db->query($query, $values);
+
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                return $result;
+            }
+        }
+
+        return true;
+    }
+    
+    /**
+     * Deletes all postings and accounts.
+     *
+     * @param mixed $accounts  boolean or account_type
+     * @param mixed $accounts  boolean or posting_type.
+     *
+     * @return mixed                True or PEAR_Error
+     */
+    function _deleteAll($accounts, $postings)
+    {
+        /* Delete postings. */
+        if ($postings) {
+            $query = sprintf('DELETE FROM %s WHERE posting_owner = ?',
+                             $this->_params['table_postings']);
+            $values = array($this->_ledger);
+            
+            /* Filter. */
+            if ($postings !== true) {
+                $query .= ' AND posting_type = ?';
+                $values[] = $postings;
+            }
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Fima_Driver_sql::_deleteAll(): %s', $query),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            /* Attempt the delete query. */
+            $result = $this->_db->query($query, $values);
+
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                return $result;
+            }
+        } else {
+            /* If postings aren't deleted, don't delete accounts. */
+            return false;
+        }
+        
+        /* Delete Accounts */
+        if ($accounts) {
+            $query = sprintf('DELETE FROM %s WHERE account_owner = ?',
+                             $this->_params['table_accounts']);
+            $values = array($this->_ledger);
+
+            /* Filter. */
+            if ($accounts !== true) {
+                $query .= ' AND account_type = ?';
+                $values[] = $accounts;
+            }
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Fima_Driver_sql::_deleteAll(): %s', $query),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            /* Attempt the delete query. */
+            $result = $this->_db->query($query, $values);
+
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                return $result;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Build the where clause for a query using the passed filters
+     * Attention: does not include the WHERE keyword, add WHERE 1=1 manually in the query
+     * 
+     * @param array $filters  Array of filters, syntax: array(field, value [, operator = '=' [, andor = 'AND']])
+     * @param array $prefix   optional prefix for fields
+     *
+     * @return integer   number of added filters
+     */
+    function _addFilters($filters, &$query, &$values, $prefix = '')
+    {
+        $filtercnt = 0;
+        
+        foreach ($filters as $filter) {
+            // and/or
+            if (!isset($filter[3])) {
+                $filter[3] = 'AND';
+            } else {
+                $filter[3] = strtoupper($filter[3]);
+                if (!in_array($filter[3], array('AND', 'OR'))) {
+                    $filter[3] = 'AND';
+                }
+            }
+            
+            // subfilter
+            if (is_array($filter[0])) {
+                $query .= ' ' . $filter[3] . ' (1=1';
+                $filtercnt += $this->_addFilters($filter[0], $query, $values, $prefix);
+                $query .= ')';
+                continue;
+            }
+            
+            // fix operator
+            if (!isset($filter[2])) {
+                $filter[2] = '=';
+            } else {
+                $filter[2] = strtoupper($filter[2]);
+                if (!in_array($filter[2], array('<', '>', '<=', '>=', '=', '<>', '!=', 'IN', 'NOT IN', 'IS', 'IS NOT', 'LIKE', 'NOT LIKE'))) {
+                    $filter[2] = '=';
+                }
+            }
+             
+            // fix operator for null values
+            if ($filter[1] === null) {
+                if (!in_array($filter[2], array('IS', 'IS NOT'))) {
+                    $filter[2] = in_array($filter[2], array('=', 'IN', 'LIKE')) ? 'IS' : 'IS NOT';
+                }
+            } elseif (in_array($filter[2], array('IS', 'IS NOT'))) {
+                $filter[2] = ($filter[2] == 'IS') ? '=' : '!=';
+            }
+            
+            // fix operator for array value + prepare values
+            if (is_array($filter[1])) {
+                if (!in_array($filter[2], array('IN', 'NOT IN'))) {
+                   $filter[2] = in_array($filter[2], array('=', 'IS', 'LIKE')) ? 'IN' : 'NOT IN';
+                }
+                $filterph = '(!)';
+                foreach ($filter[1] as $key => $value) {
+                    $filter[1][$key] = $this->_db->quoteSmart($value);
+                }
+                $filter[1] = implode(',', $filter[1]);
+            } else {
+                if (in_array($filter[2], array('IN', 'NOT IN'))) {
+                    $filter[2] = ($filter[2] == 'IN') ? '=' : '!=';
+                }
+                $filterph = '?';
+            }
+            
+            // fix != operator
+            if ($filter[2] == '!=') {
+                $filter[2] = '<>';
+            }
+                                
+            $query .= sprintf(' ' . $filter[3] . ' ' . $prefix . '%s %s %s', $filter[0], $filter[2], $filterph);
+            $values[] = $filter[1];
+            $filtercnt++;
+        }
+        
+        return $filtercnt;
+    }
+
+    /**
+     * Attempts to open a connection to the SQL server.
+     *
+     * @return boolean  True on success; PEAR_Error on failure.
+     */
+    function initialize()
+    {
+        Horde::assertDriverConfig($this->_params, 'storage',
+                                  array('phptype', 'charset'));
+
+        if (!isset($this->_params['database'])) {
+            $this->_params['database'] = '';
+        }
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = '';
+        }
+        if (!isset($this->_params['hostspec'])) {
+            $this->_params['hostspec'] = '';
+        }
+
+        /* Connect to the SQL server using the supplied parameters. */
+        require_once 'DB.php';
+        $this->_db = &DB::connect($this->_params,
+                                  array('persistent' => !empty($this->_params['persistent'])));
+        if (is_a($this->_db, 'PEAR_Error')) {
+            return $this->_db;
+        }
+
+        /* Set DB portability options. */
+        switch ($this->_db->phptype) {
+        case 'mssql':
+            $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+            break;
+        default:
+            $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+        }
+
+        $this->_connected = true;
+
+        return true;
+    }
+
+}
diff --git a/fima/lib/Fima.php b/fima/lib/Fima.php
new file mode 100644 (file)
index 0000000..6fcdc8e
--- /dev/null
@@ -0,0 +1,785 @@
+<?php
+
+/**
+ * Sort by.
+ */
+define('FIMA_SORT_DATE',    'date');
+define('FIMA_SORT_ASSET',   'asset_number');
+define('FIMA_SORT_ACCOUNT', 'account_number');
+define('FIMA_SORT_AMOUNT',  'amount');
+define('FIMA_SORT_DESC',    'desc');
+
+/**
+ * Order by.
+ */
+define('FIMA_SORT_ASCEND',  0);
+define('FIMA_SORT_DESCEND', 1);
+
+/**
+ * Account types.
+ */
+define('FIMA_ACCOUNTTYPE_ASSET',     'asset');
+define('FIMA_ACCOUNTTYPE_INCOME',    'income');
+define('FIMA_ACCOUNTTYPE_EXPENSE',   'expense');
+
+/**
+ * Posting types.
+ */
+define('FIMA_POSTINGTYPE_ACTUAL',   'actual');
+define('FIMA_POSTINGTYPE_FORECAST', 'forecast');
+define('FIMA_POSTINGTYPE_BUDGET',   'budget');
+
+/**
+ * Fima Base Class.
+ *
+ * $Horde: fima/lib/Fima.php,v 1.1 2009/03/11 17:46:00 trt Exp $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima {
+
+    /**
+     * Retrieves the current user's ledgers from storage.
+     * This function will also sort the resulting list, if requested.
+     *
+     * @param boolean $filters  Filters for accounts.
+     *
+     * @return array            A list of the requested accounts.
+     *
+     * @see Fima_Driver::listAccounts()
+     */
+    function listAccounts($filters = array())
+    {
+        $ledger = Fima::getActiveLedger();
+
+        /* Create a Fima storage instance. */
+        $storage = &Fima_Driver::singleton($ledger);
+        $storage->retrieveAccounts($filters);
+
+        /* Retrieve the accounts from storage. */
+        $accounts = $storage->listAccounts();
+        if (is_a($accounts, 'PEAR_Error')) {
+            return $accounts;
+        }
+
+        return $accounts;
+    }
+    
+    function getAccount($account)
+    {
+        $ledger = Fima::getActiveLedger();
+        $storage = &Fima_Driver::singleton($ledger);
+        return $storage->getAccount($account);
+    }
+
+    /**
+     * Retrieves the current user's postings from storage.
+     *
+     * @param boolean $filters  Filters for postings.
+     * @param integer $page     Page/Recordsset to display.
+     *
+     * @return array            A list of the requested postings.
+     *
+     * @see Fima_Driver::listPostings()
+     */
+    function listPostings($filters = array(), $page = null)
+    {
+        global $prefs;
+
+        $ledger = Fima::getActiveLedger();
+        $postingtype = $prefs->getValue('active_postingtype');
+        if ($page == 0) {
+            $limit = null;
+        } else {
+            $limit = array($page, (int)$prefs->getValue('max_postings'));
+        }
+
+        /* Create a Fima storage instance. */
+        $storage = &Fima_Driver::singleton($ledger);
+        $storage->retrievePostings($filters, 
+                                   array('posting_' . $prefs->getValue('sortby') . ' ' . ($prefs->getValue('sortdir') ? 'DESC' : 'ASC'),
+                                         'posting_' . $prefs->getValue('altsortby')),
+                                   $limit);
+
+        /* Retrieve the accounts from storage. */
+        $postings = $storage->listPostings();
+        if (is_a($postings, 'PEAR_Error')) {
+            return $postings;
+        }
+
+        return $postings;
+    }
+
+    /**
+     * Get the total number of postings with the selected filters.
+     *
+     * @return int  The total number of postings.
+     */
+    function getPostingsCount()
+    {
+        $ledger = Fima::getActiveLedger();
+        
+        /* Create a Fima storage instance. */
+        $storage = &Fima_Driver::singleton($ledger);
+        return $storage->_postingsCount;
+    }
+    
+    
+    /**
+     * Get grouped results from storage.
+     *
+     * @param array $groups     Fields to group.
+     * @param boolean $filters  Filters for postings.
+     *
+     * @return array            A matrix of the grouped results.
+     *
+     * @see Fima_Driver::listPostings()
+     */
+    function getResults($groups = array(), $filters = array()) {
+        $ledger = Fima::getActiveLedger();
+        
+        /* Create a Fima storage instance. */
+        $storage = &Fima_Driver::singleton($ledger);
+        return $storage->getResults($groups, $filters);
+    }
+
+    /**
+     * Get the results of all asset accounts.
+     *
+     * @param string $postingtype  Type of postings.
+     * @param boolean $perdate     Date of asset results.
+     *
+     * @return array  Array of asset accounts and results
+     */
+    function getAssetResults($postingtype, $perdate = null)
+    {
+        $ledger = Fima::getActiveLedger();
+        
+        /* Create a Fima storage instance. */
+        $storage = &Fima_Driver::singleton($ledger);
+        return $storage->getAssetResults($postingtype, $perdate);
+    }
+
+    /**
+     * Get the total result of postings with the selected filters.
+     *
+     * @return float  The total result of postings.
+     */
+    function getPostingsResult()
+    {
+        $ledger = Fima::getActiveLedger();
+        
+        /* Create a Fima storage instance. */
+        $storage = &Fima_Driver::singleton($ledger);
+        return $storage->_postingsResult;
+    }
+
+    /**
+     * Lists all ledgers a user has access to.
+     *
+     * @param boolean $owneronly  Only return ledgers that this user owns?
+     *                            Defaults to false.
+     * @param integer $permission The permission to filter ledgers by.
+     *
+     * @return array  The list of ledgers.
+     */
+    function listLedgers($owneronly = false, $permission = PERMS_SHOW)
+    {
+        $ledgers = $GLOBALS['fima_shares']->listShares(Auth::getAuth(), $permission, $owneronly ? Auth::getAuth() : null);
+        if (is_a($ledgers, 'PEAR_Error')) {
+            Horde::logMessage($ledgers, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return array();
+        }
+
+        return $ledgers;
+    }
+
+    /**
+     * Returns the active ledger for the current user.
+     */
+    function getActiveLedger($permission = PERMS_SHOW)
+    {
+        global $prefs;
+
+        $active_ledger = $prefs->getValue('active_ledger');
+        $ledgers = Fima::listLedgers(false, $permission);
+
+        if (isset($ledgers[$active_ledger])) {
+            return $active_ledger;
+        } elseif ($prefs->isLocked('active_ledger')) {
+            return false;
+        } elseif (count($ledgers)) {
+            return key($ledgers);
+        }
+
+        return false;
+    }
+
+    /**
+     * Get parent account number
+     */
+    function getAccountParent($accountNumber)
+    {
+        if ($accountNumber % 100 == 0) {
+            $parent = null;
+        } else {
+            $parent = sprintf('%\'04d', (int)($accountNumber / 100) * 100);
+        }
+        return $parent;
+    }
+
+    /**
+     * Builds the HTML for a account selection widget.
+     *
+     * @param string $name           The name of the widget.
+     * @param mixed $value           The value(s) to select by default.
+     * @param string $params         Any additional parameters to include in the <select> tag.
+     * @param mixed $blank           False or label of blank entry.
+     * @param boolean $multiple      Shall multiple selections be enabled?
+     * @param array $filters         Filters for acconuts.
+     * @param boolean $hideclosed    Do not include closed accounts.
+     *
+     * @return string  The HTML <select> widget.
+     */
+    function buildAccountWidget($name, $value = '', $params = null, $blank = false, $multiple = false, $filters = array(), $hideclosed = false)
+    {
+        $accounts = Fima::listAccounts($filters);
+        
+        $html = '<select id="' . $name . '" name="' . $name . '"';
+        if (!is_null($params)) {
+            $html .= ' ' . $params;
+        }
+        if ($multiple) {
+            $html .= ' multiple="multiple" size="' . min(5, count($accounts)). '"';
+        }
+        $html .= '>';
+        if ($blank !== false) {
+            $html .= '<option value="">' . htmlspecialchars($blank) . '</option>';
+        }
+        
+        $accounts = Fima::listAccounts($filters);
+        foreach ($accounts as $accountId => $account) {
+            if ($hideclosed && $account['closed'] && $accountId != $value) {
+                continue;
+            }
+            $html .= '<option value="' . $accountId . '" class="' . ($account['eo'] ? 'eo' : '') . $account['type'] . ' ' . ($account['closed'] ? 'closed' : '') . '"';
+            if ($multiple && is_array($value)) {
+                $html .= (in_array($accountId, $value)) ? ' selected="selected">' : '>';
+            } else {
+                $html .= ($accountId == $value) ? ' selected="selected">' : '>';
+            }
+            $html .= htmlspecialchars($account['label']) . '</option>';
+        }
+        
+        return $html . "</select>\n";
+    }
+
+    /**
+     * Builds the HTML for a account type selection widget.
+     *
+     * @param string $name       The name of the widget.
+     * @param string $value      The value to select by default.
+     * @param string $params     Any additional parameters to include in the <select >tag.
+     * @param boolean $blank     Shall a blank entry be added?
+     * @param boolean $multiple  Shall multiple selections be inabled?
+     *
+     * @return string  The HTML <select> widget.
+     */
+    function buildAccountTypeWidget($name, $value = '', $params = null, $blank = false, $multiple = false)
+    {
+        $types = Fima::getAccountTypes();
+
+        $html = '<select id="' . $name . '" name="' . $name . '"';
+        if (!is_null($params)) {
+            $html .= ' ' . $params;
+        }
+        if ($multiple) {
+            $html .= ' multiple="multiple" size="' . min(5, count($types)). '"';
+        }
+        $html .= '>';
+        if ($blank !== false) {
+            $html .= '<option value="">' . htmlspecialchars($blank) . '</option>';
+        }
+        
+        foreach ($types as $typeValue => $typeLabel) {
+            $html .= '<option value="' . $typeValue . '"';
+            if ($multiple && is_array($value)) {
+                $html .= (in_array($accountId, $value)) ? ' selected="selected">' : '>';
+            } else {
+                $html .= ($typeValue == $value) ? ' selected="selected">' : '>';
+            }
+            $html .= htmlspecialchars($typeLabel) . '</option>';
+        }
+        
+        return $html . "</select>\n";
+    }
+
+    /**
+     * Builds the HTML for a posting type selection widget.
+     *
+     * @param string $name       The name of the widget.
+     * @param string $value      The value to select by default.
+     * @param string $params     Any additional parameters to include in the <select >tag.
+     * @param boolean $blank     Shall a blank entry be added?
+     * @param boolean $multiple  Shall multiple selections be inabled?
+     *
+     * @return string  The HTML <select> widget.
+     */
+    function buildPostingTypeWidget($name, $value = '', $params = null, $blank = false, $multiple = false)
+    {
+        $types = Fima::getPostingTypes();
+
+        $html = '<select id="' . $name . '" name="' . $name . '"';
+        if (!is_null($params)) {
+            $html .= ' ' . $params;
+        }
+        if ($multiple) {
+            $html .= ' multiple="multiple" size="' . min(5, count($types)). '"';
+        }
+        $html .= '>';
+        if ($blank !== false) {
+            $html .= '<option value="">' . htmlspecialchars($blank) . '</option>';
+        }
+        
+        foreach ($types as $typeValue => $typeLabel) {
+            $html .= '<option value="' . $typeValue . '"';
+            if ($multiple && is_array($value)) {
+                $html .= (in_array($accountId, $value)) ? ' selected="selected">' : '>';
+            } else {
+                $html .= ($typeValue == $value) ? ' selected="selected">' : '>';
+            }
+            $html .= htmlspecialchars($typeLabel) . '</option>';
+        }
+        
+        return $html . "</select>\n";
+    }
+
+    /**
+     * Builds the HTML for a date selection widget.
+     *
+     * @param string $name        The name of the widget.
+     * @param integer $value      The value to select by default.
+     * @param string $params      Any additional parameters to include in the <select> tag.
+     * @param boolean $blank      Shall a blank entry be added?
+     * @param string $periodonly  Don't display the day input field
+     *
+     * @return string  The HTML <select> widget.
+     */
+    function buildDateWidget($name, $value = 0, $params = null, $blank = false, $periodonly = false)
+    {
+        $value = ($value !== 0) ? explode('-', date('Y-n-j', $value)) : array(date('Y'), 0, 0);
+        
+        /* Year. */
+        $html = '<input id="' . $name . '[year]" name="' . $name . '[year]" type="text" value="' . $value[0] . '" onchange="updateWday(\'' . $name . '\');" size="4" maxlength="4"';
+        if (!is_null($params)) {
+            $html .= ' ' . $params;
+        }
+        $html .= ' />' . "\n";
+        
+        /* Month. */
+        $html .= '- <select id="' . $name . '[month]" name="' . $name . '[month]" onchange="updateWday(\'' . $name . '\');"';
+        if (!is_null($params)) {
+            $html .= ' ' . $params;
+        }
+        $html .= '>';
+        if ($blank !== false) {
+            $html .= '<option value="">' . htmlspecialchars($blank) . '</option>';
+        }
+        for ($i = 1; $i < 13; ++$i) {
+            $html .= '<option value="' . $i . '"';
+            $html .= ($i == $value[1]) ? ' selected="selected">' : '>';
+            $html .= htmlspecialchars(strftime('%b', mktime(0, 0, 0, $i, 1))) . '</option>';
+        }
+        $html .= '</select>' . "\n";
+         
+        /* Period only? */
+        if ($periodonly) {
+            return $html;
+        }
+        
+        /* Day. */
+        $html .= '- <select id="' . $name . '[day]" name="' . $name . '[day]" onchange="updateWday(\'' . $name . '\');"';
+        if (!is_null($params)) {
+            $html .= ' ' . $params;
+        }
+        $html .= '>';
+        if ($blank !== false) {
+            $html .= '<option value="">' . htmlspecialchars($blank) . '</option>';
+        }
+        for ($i = 1; $i < 32; ++$i) {
+            $html .= '<option value="' . $i . '"';
+            $html .= ($i == $value[2]) ? ' selected="selected">' : '>';
+            $html .= $i . '</option>';
+        }
+        $html .= '</select>' . "\n";
+
+        /* Weekday + Calendar. */
+        if ($GLOBALS['browser']->hasFeature('dom')) {
+            $html .= '<span id="' . $name . '_wday" ></span><script type="text/javascript">updateWday(\'' . $name . '\');</script>' . "\n";
+            $html .= '</td><td>';
+            $html .= Horde::addScriptFile('open_calendar.js', 'horde');
+            if (!isset($GLOBALS['fima_gotocontrol'])) {
+                $GLOBALS['fima_gotocontrol'] = true;
+                $html .= '<div id="goto" class="control" style="position:absolute;visibility:hidden;padding:1px"></div>' . "\n";
+            }
+            $html .= '<a href="#" onclick="openCalendar(\'' . $name . '_img\', \'' . $name . '\', \'updateWday(\\\'' . $name . '\\\');\'); return false;" title="Select a date">';
+            $html .= Horde::img('calendar.png', _("Set date"), 'align="top" id="' . $name . '_img"', $GLOBALS['registry']->getImageDir('horde'));
+            $html .= '</a>' . "\n";
+        }
+                 
+        return $html;
+    }
+    
+    /**
+     * Get account types.
+     *
+     * @param string $accountType  Get a specific account type or all.
+     *
+     * @return mixed  Array of account types or a specific account type.
+     */
+    function getAccountTypes($accountType = null)
+    {
+        $types = array(FIMA_ACCOUNTTYPE_ASSET     => _("Asset"),
+                       FIMA_ACCOUNTTYPE_INCOME    => _("Income"),
+                       FIMA_ACCOUNTTYPE_EXPENSE   => _("Expense"));
+
+        if ($accountType !== null) {
+            if (isset($types[$accountType])) {
+                return $types[$accountType];
+            } else {
+                return null;
+            }
+        } else {
+            return $types;
+        }
+    }
+    
+    /**
+     * Get posting types.
+     *
+     * @param string $postingType  Get a specific posting type or all.
+     *
+     * @return mixed  Array of posting types or a specific posting type.
+     */
+    function getPostingTypes($postingType = null)
+    {
+        $types = array(FIMA_POSTINGTYPE_ACTUAL   => _("Actual"),
+                       FIMA_POSTINGTYPE_FORECAST => _("Forecast"),
+                       FIMA_POSTINGTYPE_BUDGET   => _("Budget"));
+
+        if ($postingType !== null) {
+            if (isset($types[$postingType])) {
+                return $types[$postingType];
+            } else {
+                return null;
+            }
+        } else {
+            return $types;
+        }
+    }
+    
+    /**
+     * Convert an amount from the interface to a float value.
+     *
+     * @param string $amount  Amount to convert.
+     *
+     * @return float  Float value of the amount.
+     */
+    function convertAmountToValue($amount)
+    {
+        global $prefs;
+        
+        $format = $prefs->getValue('amount_format');
+        return (float)str_replace(array($format{0}, $format{1}), array('', '.'), $amount);
+    }
+    
+    /**
+     * Convert a float number to an amount for the interface.
+     *
+     * @param float $value  Float value to convert.
+     *
+     * @return string  Amount.
+     */
+    function convertValueToAmount($value)
+    {
+        global $prefs;
+        
+        $format = $prefs->getValue('amount_format');
+        return number_format($value, 2, $format{1}, $format{0});
+    }
+    
+    /*
+     * Convert a formatted date to a unix timestamp.
+     *
+     * @param string $date    Formatted date.
+     * @param string $format  Date format.
+     *
+     * @return int  Unix timestamp.
+     */
+    function convertDateToStamp($date, $format)
+    {
+        if ($date == '') {
+            return false;
+        }
+        
+        if (preg_match('/[^%a-zA-Z]/', $format, $seperator) === false) {
+            return false;
+        }
+        
+        $formatparts = explode($seperator[0], $format);
+        $dateparts = explode($seperator[0], $date);
+        
+        foreach ($formatparts as $key => $fmt) {
+            $dateparts[$fmt] = $dateparts[$key];
+        }
+        
+        $stamp = mktime(0, 0, 0, $dateparts['%m'], $dateparts['%d'], $dateparts['%Y']);
+        
+        return $stamp;
+    }
+    
+    /**
+     * Convert a date format to a format useable when entering postings.
+     *
+     * @param string $format  Date format.
+     *
+     * @return string  The converted date format.
+     */
+    function convertDateFormat($format)
+    {
+        switch($format) {
+        case '%x':
+        case '%Y-%m-%d':
+        case '%d/%m/%Y':
+        case '%d.%m.%Y':
+        case '%m/%d/%Y':
+            break;
+        case '%a %Y-%m-%d':
+            $format = '%Y-%m-%d';
+            break;
+        case '%A, %d %B %Y':
+        case '%a, %e %b %Y':
+        case '%a, %e %b %y':
+        case '%a %d %b %Y':
+        case '%e %b %Y':
+        case '%e. %m %Y':
+            $format = '%d/%m/%Y';
+            break;
+        case '%A, %d. %B %Y':
+        case '%e. %b %Y':
+        case '%e. %m.':
+        case '%e. %B':
+        case '%e. %B %Y':
+        case '%e. %B %y':
+            $format = '%d.%m.%Y';
+            break;
+        case '%A %B %d, %Y':
+        case '%a, %b %e, %Y':
+        case '%a, %b %e, %y':
+        case '%a, %b %e':
+        case '%B %e, %Y':
+            $format = '%m/%d/%Y';
+            break;
+        case '%a %x':
+        default:
+            $format = '%x';
+            break;
+        }
+    
+        if ($format == '%x') {
+            $fmts = array('%Y-%m-%d', '%d/%m/%Y', '%d.%m.%Y', '%m/%d/%Y');
+            foreach ($fmts as $fmt) {
+                if (strftime($format) == strftime($fmt)) {
+                    $format = $fmt;
+                    break;
+                }
+            }
+            if ($format == '%x') {
+                $format = $fmts[0];
+            }
+        }
+
+        return $format;
+    }
+    
+    /**
+     * Convert a date format to a period format useable for reports.
+     *
+     * @param string $format  Date format.
+     *
+     * @return string  The converted period format.
+     */
+    function convertDateToPeriodFormat($format)
+    {
+        if ($format == '%x') {
+            $fmts = array('%Y-%m-%d', '%d/%m/%Y', '%d.%m.%Y', '%m/%d/%Y');
+            foreach ($fmts as $fmt) {
+                if (strftime($format) == strftime($fmt)) {
+                    $format = $fmt;
+                    break;
+                }
+            }
+            if ($format == '%x') {
+                $format = $fmts[0];
+            }
+        }
+        $format .= ' ';
+
+        $p = preg_match_all('/(%[YymBb])(.)/', $format, $matches);
+        $format = '';
+        for ($i = 0; $i < $p; $i++) {
+            $format .= $matches[1][$i] . (($i < $p - 1) ? $matches[2][$i] : '');
+        }
+
+        return $format;
+    }
+    
+    /**
+     * Convert wildcards in a text to SQL wildcards.
+     *
+     * @param string $text  Text containing wildcards.
+     *
+     * @return string  Converted text with SQL wildcards.
+     */
+    function convertWildcards($text)
+    {
+        global $prefs;
+        
+        $wildcards = $prefs->getValue('wildcard_format');
+        if ($wildcards == 'dos') {
+            $text = str_replace(array('\\*', '\\?'), array(chr(0xe), chr(0xf)), $text);
+            $text = str_replace(array('%', '_'), array('\\%', '\\_'), $text);
+            $text = str_replace(array('*', '?'), array('%', '_'), $text);
+            $text = str_replace(array(chr(0xe), chr(0xf)), array('*', '?'), $text);
+        } elseif ($wildcards == 'sql') {
+        } elseif ($wildcards == 'none') {
+            $text = str_replace(array('%', '_'), array('\\%', '\\_'), $text);
+        }
+        return $text;
+    }
+    
+    /**
+     * Initial app setup code.
+     */
+    function initialize()
+    {
+        /* Store the request timestamp if it's not already present. */
+        if (!isset($_SERVER['REQUEST_TIME'])) {
+            $_SERVER['REQUEST_TIME'] = time();
+        }
+
+        // Update the preference for what ledgers to display. If the user
+        // doesn't have any selected ledger for view then fall back to
+        // some available ledger.
+        $GLOBALS['display_ledgers'] = @unserialize($GLOBALS['prefs']->getValue('display_ledgers'));
+        if (!$GLOBALS['display_ledgers']) {
+            $GLOBALS['display_ledgers'] = array();
+        }
+        if (($ledgerId = Util::getFormData('display_ledger')) !== null) {
+            if (is_array($ledgerId)) {
+                $GLOBALS['display_ledgers'] = $ledgerId;
+            } else {
+                if (in_array($ledgerId, $GLOBALS['display_ledgers'])) {
+                    $key = array_search($ledgerId, $GLOBALS['display_ledgers']);
+                    unset($GLOBALS['display_ledgers'][$key]);
+                } else {
+                    $GLOBALS['display_ledgers'][] = $ledgerId;
+                }
+            }
+        }
+
+        // Make sure all ledgers exist now, to save on checking later.
+        $_temp = $GLOBALS['display_ledgers'];
+        $GLOBALS['all_ledgers'] = Fima::listLedgers();
+        $GLOBALS['display_ledgers'] = array();
+        foreach ($_temp as $id) {
+            if (isset($GLOBALS['all_ledgers'][$id])) {
+                $GLOBALS['display_ledgers'][] = $id;
+            }
+        }
+
+        if (count($GLOBALS['display_ledgers']) == 0) {
+            $ledgerss = Fima::listLedgers(true);
+            if (!Auth::getAuth()) {
+                /* All ledgers for guests. */
+                $GLOBALS['display_ledgers'] = array_keys($ledgers);
+            } else {
+                /* Make sure at least the active ledger is visible. */
+                $active_ledger = Fima::getActiveLedger(PERMS_READ);
+                if ($active_ledger) {
+                    $GLOBALS['display_ledgers'] = array($active_ledger);
+                }
+
+                /* If the user's personal ledger doesn't exist, then create it. */
+                if (!$GLOBALS['fima_shares']->exists(Auth::getAuth())) {
+                    require_once 'Horde/Identity.php';
+                    $identity = &Identity::singleton();
+                    $name = $identity->getValue('fullname');
+                    if (trim($name) == '') {
+                        $name = Auth::removeHook(Auth::getAuth());
+                    }
+                    $share = &$GLOBALS['fima_shares']->newShare(Auth::getAuth());
+                    $share->set('name', sprintf(_("%s's Ledger"), $name));
+                    $GLOBALS['fima_shares']->addShare($share);
+
+                    /* Make sure the personal ledger is displayed by default. */
+                    if (!in_array(Auth::getAuth(), $GLOBALS['display_ledgers'])) {
+                        $GLOBALS['display_ledgers'][] = Auth::getAuth();
+                    }
+                }
+            }
+        }
+
+        $GLOBALS['prefs']->setValue('display_ledgers', serialize($GLOBALS['display_ledgers']));
+        
+        /* Update active ledger. */
+        if (($changeledger = Util::getFormData('changeledger')) !== null) {
+            $GLOBALS['prefs']->setValue('active_ledger', $changeledger);
+        }
+    }
+
+    /**
+     * Build Fima's list of menu items.
+     */
+    function getMenu($returnType = 'object')
+    {
+        global $conf, $registry, $browser, $print_link;
+        
+        $actionID = Util::getFormData('actionID');
+        
+        require_once 'Horde/Menu.php';
+        $menu = new Menu(HORDE_MENU_MASK_ALL);
+        $menu->add(Horde::applicationUrl('postings.php'), _("_List Postings"), 'list.png', null, null, null, (basename($_SERVER['PHP_SELF']) == 'index.php' && basename(dirname($_SERVER['PHP_SELF'])) != 'ledgers') ? 'current' : ($actionID === null ? null : '__noselection'));
+        $menu->add(Util::addParameter(Horde::applicationUrl('postings.php'), 'actionID', 'add_postings'), _("Add _Postings"), 'add.png', null, null, null, $actionID == 'add_postings' ? 'current' : '__noselection');
+        $menu->add(Horde::applicationUrl('search.php'), _("Search"), 'search.png', $registry->getImageDir('horde'));
+        $menu->add(Horde::applicationUrl('accounts.php'), _("_Accounts"), 'accounts.png');
+        
+        if (Auth::getAuth()) {
+            $menu->add(Horde::applicationUrl('ledgers/index.php'), _("_My Ledgers"), 'accounts.png');
+        }
+        
+        /* Reports. */
+        $menu->add(Horde::applicationUrl('report.php'), _("_Reports"), 'report.png');
+        
+        /* Import/Export. */
+        $menu->add(Horde::applicationUrl('data.php'), _("_Import/Export"), 'data.png', $registry->getImageDir('horde'));
+
+        /* Print. */
+        if (isset($print_link)) {
+            $menu->add($print_link, _("_Print"), 'print.png', $registry->getImageDir('horde'), '_blank', 'popup(this.href); return false;', '__noselection');
+        }
+
+        if ($returnType == 'object') {
+            return $menu;
+        } else {
+            return $menu->render();
+        }
+    }
+
+}
diff --git a/fima/lib/Forms/CreateLedger.php b/fima/lib/Forms/CreateLedger.php
new file mode 100644 (file)
index 0000000..6b0633a
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Horde_Form for creating ledgers.
+ *
+ * $Horde: fima/lib/Forms/CreateLedger.php,v 1.0 2008/06/19 18:12:08 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Fima_CreateLedgerForm class provides the form for
+ * creating a ledger.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_CreateLedgerForm extends Horde_Form {
+
+    function Fima_CreateLedgerForm(&$vars)
+    {
+        parent::Horde_Form($vars, _("Create Ledger"));
+
+        $this->addVariable(_("Name"), 'name', 'text', true);
+        $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60));
+
+        $this->setButtons(array(_("Create")));
+    }
+
+    function execute()
+    {
+        // Create new share.
+        $ledger = $GLOBALS['fima_shares']->newShare(md5(microtime()));
+        if (is_a($ledger, 'PEAR_Error')) {
+            return $ledger;
+        }
+        $ledger->set('name', $this->_vars->get('name'));
+        $ledger->set('desc', $this->_vars->get('description'));
+        return $GLOBALS['fima_shares']->addShare($ledger);
+    }
+
+}
diff --git a/fima/lib/Forms/DeleteLedger.php b/fima/lib/Forms/DeleteLedger.php
new file mode 100644 (file)
index 0000000..b33e770
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Horde_Form for deleting ledgers.
+ *
+ * $Horde: fima/lib/Forms/DeleteLedger.php,v 1.0 2008/06/19 18:15:08 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Fima_DeleteLedgerForm class provides the form for
+ * deleting a ledger.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_DeleteLedgerForm extends Horde_Form {
+
+    /**
+     * Ledger being deleted
+     */
+    var $_ledger;
+
+    function Fima_DeleteLedgerForm(&$vars, &$ledger)
+    {
+        $this->_ledger = &$ledger;
+        parent::Horde_Form($vars, sprintf(_("Delete %s"), $ledger->get('name')));
+
+        $this->addHidden('', 'l', 'text', true);
+        $this->addVariable(sprintf(_("Really delete the ledger \"%s\"? This cannot be undone and all data on this ledger will be permanently removed."), $this->_ledger->get('name')), 'desc', 'description', false);
+
+        $this->setButtons(array(_("Delete"), _("Cancel")));
+    }
+
+    function execute()
+    {
+        // If cancel was clicked, return false.
+        if ($this->_vars->get('submitbutton') == _("Cancel")) {
+            return false;
+        }
+
+        if ($this->_ledger->get('owner') != Auth::getAuth()) {
+            return PEAR::raiseError(_("Permission denied"));
+        }
+
+        // Delete the ledger.
+        $storage = &Fima_Driver::singleton($this->_ledger->getName());
+        $result = $storage->deleteAll();
+        if (is_a($result, 'PEAR_Error')) {
+            return PEAR::raiseError(sprintf(_("Unable to delete \"%s\": %s"), $this->_ledger->get('name'), $result->getMessage()));
+        } else {
+            // Remove share and all groups/permissions.
+            $result = $GLOBALS['fima_shares']->removeShare($this->_ledger);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        // Make sure we still own at least one ledger.
+        if (count(Fima::listLedgers(true)) == 0) {
+            // If the default share doesn't exist then create it.
+            if (!$GLOBALS['fima_shares']->exists(Auth::getAuth())) {
+                require_once 'Horde/Identity.php';
+                $identity = &Identity::singleton();
+                $name = $identity->getValue('fullname');
+                if (trim($name) == '') {
+                    $name = Auth::removeHook(Auth::getAuth());
+                }
+                $ledger = &$GLOBALS['fima_shares']->newShare(Auth::getAuth());
+                if (is_a($ledger, 'PEAR_Error')) {
+                    return;
+                }
+                $ledger->set('name', sprintf(_("%s's Ledger"), $name));
+                $GLOBALS['fima_shares']->addShare($ledger);
+            }
+        }
+
+        return true;
+    }
+
+}
diff --git a/fima/lib/Forms/EditLedger.php b/fima/lib/Forms/EditLedger.php
new file mode 100644 (file)
index 0000000..fb87995
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Horde_Form for editing ledgers.
+ *
+ * $Horde: fima/lib/Forms/EditLedger.php,v 1.0 2008/06/19 18:22:08 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Fima_EditLedgerForm class provides the form for
+ * editing a ledger.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_EditLedgerForm extends Horde_Form {
+
+    /**
+     * Ledger being edited
+     */
+    var $_ledger;
+
+    function Fima_EditLedgerForm(&$vars, &$ledger)
+    {
+        $this->_ledger = &$ledger;
+        parent::Horde_Form($vars, sprintf(_("Edit %s"), $ledger->get('name')));
+
+        $this->addHidden('', 'l', 'text', true);
+        $this->addVariable(_("Name"), 'name', 'text', true);
+        $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60));
+
+        $this->setButtons(array(_("Save")));
+    }
+
+    function execute()
+    {
+        $this->_ledger->set('name', $this->_vars->get('name'));
+        $this->_ledger->set('desc', $this->_vars->get('description'));
+        $result = $this->_ledger->save();
+        if (is_a($result, 'PEAR_Error')) {
+            return PEAR::raiseError(sprintf(_("Unable to save ledger \"%s\": %s"), $id, $result->getMessage()));
+        }
+        return true;
+    }
+
+}
diff --git a/fima/lib/Forms/account.php b/fima/lib/Forms/account.php
new file mode 100644 (file)
index 0000000..21e29c0
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * This file contains all Horde_Form extensions required for editing accounts.
+ *
+ * $Horde: fima/lib/Forms/account.php,v 1.1 2009/03/11 17:12:50 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/** Horde_Form_Action */
+require_once 'Horde/Form/Action.php';
+
+/**
+ * The Fima_AccountForm class provides the form for adding and editing an account.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_AccountForm extends Horde_Form {
+
+    function Fima_AccountForm(&$vars, $title = '', $delete = false)
+    {
+        parent::Horde_Form($vars, $title);
+
+        $this->addHidden('', 'actionID', 'text', true);
+        $this->addHidden('', 'account_id', 'text', false);
+        $this->addHidden('', 'number', 'text', false);
+        $this->addHidden('', 'parent_id', 'text', false);
+
+        $this->addVariable(_("Number"), 'number_new', 'text', true, false, false, array('/\d{1,4}/', 4, 4));
+        $this->addVariable(_("Type"), 'type', 'enum', true, false, false, array(Fima::getAccountTypes()));
+        $this->addVariable(_("Name"), 'name', 'text', true);
+        $this->addVariable(_("e.o."), 'eo', 'boolean', false);
+        $this->addVariable(_("Description"), 'desc', 'longtext', false);
+        $this->addVariable(_("Closed"), 'closed', 'boolean', false);
+
+        $buttons = array(_("Save"), _("Save and New"));
+        if ($delete) {
+            $buttons[] = _("Delete this account");
+        }
+        $this->setButtons($buttons);
+    }
+
+    function renderActive()
+    {
+        return parent::renderActive(new Fima_AccountForm_Renderer(array('varrenderer_driver' => array('fima', 'fima')),  $this->_submit, 2), $this->_vars, 'account.php', 'post');
+    }
+
+}
+
+/**
+ * The Fima_AccountDeleteForm class provides the form for deleting an account.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_AccountDeleteForm extends Horde_Form {
+
+    function Fima_AccountDeleteForm(&$vars, $title = '', $edit = false)
+    {
+        parent::Horde_Form($vars, $title);
+
+        $this->addHidden('', 'actionID', 'text', true);
+        $this->addHidden('', 'account_id', 'text', false);
+        $this->addHidden('', 'number', 'text', false);
+        $this->addHidden('', 'name', 'text', false);
+        $this->addHidden('', 'type', 'text', false);
+
+        $this->addVariable(_("Postings"), 'dspostings', 'fima_dspostings', false);
+        $this->addVariable(_("Subaccounts"), 'dssubaccounts', 'fima_dssubaccounts', false);
+
+        $buttons = array(_("Delete"));
+        if ($edit) {
+            $buttons[] = _("Edit this account");
+        }
+        $this->setButtons($buttons);
+    }
+
+    function renderActive()
+    {
+        return parent::renderActive(new Fima_AccountForm_Renderer(array('varrenderer_driver' => array('fima', 'fima')),  $this->_submit, 1), $this->_vars, 'account.php', 'post');
+    }
+
+}
+class Fima_AccountForm_Renderer extends Horde_Form_Renderer {
+
+    var $buttons;
+    var $buttonspacer;
+
+    function Fima_AccountForm_Renderer($params = array(), $buttons = array(), $buttonspacer = 1)
+    {
+        parent::Horde_Form_Renderer($params);
+        $this->buttons = $buttons;
+        $this->buttonspacer = $buttonspacer;
+    }
+
+    function _renderSubmit($submit, $reset)
+    {
+?><div class="control" style="padding:1em;">
+<?php foreach($this->buttons as $key => $button): ?>
+    <input class="button <?php echo ($key < $this->buttonspacer) ? 'leftFloat' : 'rightFloat' ?>" name="submitbutton" type="submit" value="<?php echo $button ?>" />
+<?php endforeach; ?>
+    <br class="clear" />
+</div>
+<?php
+    }
+
+}
+
+/**
+ * The Horde_Form_Type_fima_dspostings class provides a form field for selecting
+ * the handling (delete/shift) of postings when deleting an account.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Horde_Form_Type_fima_dspostings extends Horde_Form_Type {
+
+    function getInfo(&$vars, &$var, &$info)
+    {
+        $ds = $var->getValue($vars);
+        if ($ds['type'] == 'delete') {
+            $info = true;
+        } elseif ($ds['type'] == 'shift') {
+            $info = $ds['account'];
+        } else {
+            $info = false;
+        }
+    }
+
+    function isValid(&$var, &$vars, $value, &$message)
+    {
+        if ($value['type'] == 'shift' && $value['account'] == $vars->get('account_id')) {
+            $message = _("Select another account where to shift postings to.");
+            return false;
+        }
+
+        return true;
+    }
+
+}
+
+/**
+ * The Horde_Form_Type_fima_dssubaccounts class provides a form field for selecting
+ * the handling (delete/shift) of subaccounts when deleting an account.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Horde_Form_Type_fima_dssubaccounts extends Horde_Form_Type {
+
+    function getInfo(&$vars, &$var, &$info)
+    {
+        $ds = $var->getValue($vars);
+        if ($ds['type'] == 'none') {
+            $info = false;
+        } elseif ($ds['type'] == 'delete') {
+            $info = true;
+        } elseif ($ds['type'] == 'shift') {
+            $info = $ds['account'];
+        } else {
+            $info = false;
+        }
+    }
+
+    function isValid(&$var, &$vars, $value, &$message)
+    {
+        if ($value['type'] == 'shift' && $value['account'] == $vars->get('account_id')) {
+            $message = _("Select another account where to shift subaccount postings to.");
+            return false;
+        }
+
+        return true;
+    }
+
+}
diff --git a/fima/lib/Report.php b/fima/lib/Report.php
new file mode 100644 (file)
index 0000000..7f87a81
--- /dev/null
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Fima_Report:: defines an API for implementing reports for Fima.
+ *
+ * $Horde: fima/lib/Report.php,v 1.0 2008/08/21 20:31:00 trt Exp $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report {
+
+    /**
+     * Hash containing report parameters.
+     *
+     * @var array
+     */
+    var $_params = array();
+    
+    /**
+     * Array containing the data after execution of the report.
+     *
+     * @var mixed
+     */
+    var $_data = array();
+    
+    /**
+     * Bytes containing the report graph after execution of the report.
+     *
+     * @var bytes
+     */
+    var $_graph = null;
+    
+    /**
+     * Constructor - just store the $params in our newly-created
+     * object. All other work is done by initialize().
+     *
+     * @param array $params  Any parameters needed for this driver.
+     */
+    function Fima_Report($params = array(), $errormsg = null)
+    {
+        $this->_params = $params;
+        if (is_null($errormsg)) {
+            $this->_errormsg = _("The Finances reports are not currently available.");
+        } else {
+            $this->_errormsg = $errormsg;
+        }
+    }
+
+    /**
+     * Returns a specific report parameter.
+     *
+     * @param string $param   Paramter to retrieve.
+     *
+     * @return mixed   Report parameter
+     */
+    function getParam($param)
+    {
+        if (isset($this->_params[$param])) {
+            return $this->_params[$param];
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Set a specific report parameter.
+     *
+     * @param string $param   Paramter to set.
+     * @param mixed $vale     Value to set paramter to.
+     *
+     * @return mixed   Report parameter on success, else null
+     */
+    function setParam($param, $value)
+    {
+        $this->_params[$param] = $value;
+        return $value;
+    }
+    
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function execute()
+    {
+        $execute = $this->_execute();
+        if (is_a($execute, 'PEAR_Error')) {
+            return $execute;
+        }
+
+        /* Log the execution of the report in the history log. */
+        $history = &Horde_History::singleton();
+        $history->log('fima:report:' . $this->_params['report_id'], array('action' => 'execute'), true);
+        
+        return true;
+    }
+    
+    /**
+     * Returns the data after report is executed.
+     *
+     * @return array  Data matrix
+     */
+    function getData()
+    {
+        return $this->_data;
+    }
+    
+    /**
+     * Output the graph of this report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function getGraph()
+    {
+        $execute = $this->_getGraph();
+        if (is_a($execute, 'PEAR_Error')) {
+            return $execute;
+        }
+        
+        require_once FIMA_BASE . '/lib/ReportGraph.php';
+        $graph = &Fima_ReportGraph::factory($this->_params['graph'], $this->data, $this->_params);
+        if (is_a($graph, 'PEAR_Error')) {
+            $notification->push(sprintf(_("There was a problem creating the report graph: %s."), $report->getMessage()), 'horde.error');
+            return $graph;
+        }
+        
+        /* Execute report graph. */
+        $graph->execute();
+        if (is_a($status, 'PEAR_Error')) {
+            $notification->push(sprintf(_("There was a problem executing the report graph: %s."), $status->getMessage()), 'horde.error');
+            return $status;
+        }
+        
+        $graph->getGraph();
+        return true;
+    }
+
+    /**
+     * Initialization of the report.
+     *
+     * @return boolean  True on success; PEAR_Error on failure.
+     */
+    function initialize()
+    {
+    }
+    
+    /**
+     * Attempts to return a concrete Fima_Report instance based on $driver.
+     *
+     * @param string    $driver     The type of the concrete Fima_Report subclass
+     *                              to return.  The class name is based on the
+     *                              storage driver ($driver).  The code is
+     *                              dynamically included.
+     *
+     * @param array     $params     (optional) A hash containing any additional
+     *                              configuration or connection parameters a
+     *                              subclass might need.
+     *
+     * @return mixed    The newly created concrete Fima_Driver instance, or
+     *                                 false on an error.
+     */
+    function &factory($driver = null, $params = null)
+    {
+        if ($driver === null) {
+            $report =& new Fima_Report($params, _("No report driver loaded"));
+            return $report;
+        }
+
+        require_once dirname(__FILE__) . '/Report/' . $driver . '.php';
+        $class = 'Fima_Report_' . $driver;
+        if (class_exists($class)) {
+            $report = &new $class($params);
+            $result = $report->initialize();
+            if (is_a($result, 'PEAR_Error')) {
+                $report =& new Fima_Report($params, sprintf(_("The Finances reports are not currently available: %s"), $result->getMessage()));
+            }
+        } else {
+            $report =& new Fima_Report($params, sprintf(_("Unable to load the definition of %s."), $class));
+        }
+        
+        return $report;
+    }
+
+}
\ No newline at end of file
diff --git a/fima/lib/Report/AccountOverview.php b/fima/lib/Report/AccountOverview.php
new file mode 100644 (file)
index 0000000..8f8649c
--- /dev/null
@@ -0,0 +1,288 @@
+<?php
+/**
+ * Fima_Report_AccountOverview.
+ *
+ * $Horde: fima/lib/Report/AccountOverview.php,v 1.1 2009/03/13 17:46:08 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/Report.php';
+
+/*
+ * Fima_Report_AccountOverview class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report_AccountOverview extends Fima_Report {
+
+    /*
+     * Constructs a new AccountOverview Report.
+     */
+    function Fima_Report_AccountOverview($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Get posting types. */
+        $postingtypes = Fima::getPostingTypes();
+
+        /* Params. */
+        if (($display = $this->getParam('display')) === null) {
+            return PEAR::raiseError(_("No display type"));
+        }
+        $posting_account = $this->getParam('posting_account');
+        $period_start = $this->getParam('period_start');
+        $period_end   = $this->getParam('period_end');
+        $reference_start = $this->getParam('reference_start');
+        $reference_end   = $this->getParam('reference_end');
+        $cumulate = $this->getParam('cumulate');
+        $nullrows = $this->getParam('nullrows');
+        $yearly = $this->getParam('yearly');
+        $graph = $this->getParam('graph');
+        $datefmt = $yearly ? '%Y' : Fima::convertDateToPeriodFormat($GLOBALS['prefs']->getValue('date_format'));
+        $sortby = $this->getParam('sortby');
+        $sortdir = $this->getParam('sortdir');
+
+        /* Rows. */
+        $rows = array();
+        for ($period = $period_start; $period <= $period_end; $period = strtotime($yearly ? '+1 year' : '+1 month', $period)) {
+            $rows[strftime($yearly ? '%Y' : '%Y%m', $period)] = strftime($datefmt, $period);
+        }
+
+        /* Columns. */
+        $cols = explode('_', $display);
+
+        $displaypostingtypes = array();
+        $displayreference = false;
+        $displaydiffa = false;
+        $displaydiffp = false;
+
+        $colheaders = array('__header__' => _("Period"));
+        $coldummy = array();
+        foreach ($cols as $colPos => $colId) {
+            if (isset($postingtypes[$colId])) {
+                $displaypostingtypes[] = $colId;
+                $colheaders[$colId] = $postingtypes[$colId];
+            } elseif ($colId == 'reference') {
+                $displayreference = true;
+                $colheaders[$colId] = _("Reference");
+            } elseif ($colId == 'difference') {
+                $displaydiffa = $colPos;
+                $colheaders[$colId] = _("Difference");
+            } elseif ($colId == '%') {
+                $displaydiffp = $colPos;
+                $colheaders[$colId] = _("Diff. (%)");
+            }
+            $coldummy[$colId] = 0;
+        }
+        
+
+        /* Initialize matrix. */
+        $data = array();
+        $data['__headersort__'] = $colheaders;
+        foreach ($rows as $rowId => $rowLabel) {
+            $data[$rowId] = array('__header__' => $rowLabel) + $coldummy;
+        }
+
+        /* Results. */
+        $total = array('__header__' => _("Total Result")) + $coldummy;
+        $filters = array();
+        if ($posting_account) {
+            $filters[] = array('account', $posting_account);
+        }
+        $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET, '<>');
+        $filters[] = array('type', $cols);
+        if ($period_start !== null) {
+            $filters[] = array('date', (int)$period_start, '>=');
+        }
+        if ($period_end !== null) {
+            $filters[] = array('date', (int)$period_end, '<=');
+        }
+        $result = Fima::getResults(array('type', $yearly ? 'date_year' : 'date_month'), $filters);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        foreach ($result as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $data[$rowId][$colId] = $value;
+                $total[$colId] += $value;
+            }
+        }
+        $data['__result__'] = $total;
+        
+        /* Reference. */
+        if ($displayreference) {
+            $filters = array();
+            if ($posting_account) {
+                $filters[] = array('account', $posting_account);
+            }
+            $filters[] = array('account_type', $rows);
+            $filters[] = array('type', $displaypostingtypes[0]);
+            if ($reference_start !== null) {
+                $filters[] = array('date', (int)$reference_start, '>=');
+            }
+            if ($reference_end !== null) {
+                $filters[] = array('date', (int)$reference_end, '<=');
+            }
+            
+            $result = Fima::getResults(array('type', $yearly ? 'date_year' : 'date_month'), $filters);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            foreach ($result as $rowId => $row) {
+                foreach ($row as $colId => $value) {
+                    $colId = 'reference';
+                    $data[$rowId][$colId] = $value;
+                    $data['__result__'][$colId] += $value; 
+                }
+            }
+        }
+
+        /* Difference. */
+        if ($displaydiffa > 1 || $displaydiffp > 1) {
+            $cola1 = $cols[$displaydiffa - 2];
+            $cola2 = $cols[$displaydiffa - 1];
+            $colp1 = $cols[$displaydiffp - 2];
+            $colp2 = $cols[$displaydiffp - 1];
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__header.*__/', $rowId)) {
+                    continue;
+                }
+                if ($displaydiffa > 1) {
+                    $data[$rowId]['difference'] = $data[$rowId][$cola1] - $data[$rowId][$cola2];
+                }
+                if ($displaydiffp > 1) {
+                    if ($data[$rowId][$colp1] != 0) {
+                        $data[$rowId]['%'] = $data[$rowId][$colp2] / abs($data[$rowId][$colp1]) * 100;
+                    } else {
+                        $data[$rowId]['%'] = null;
+                    }
+                }
+            }
+        }
+
+        /* Null Rows and Cumulate. */
+        if (!$nullrows || $cumulate) {
+            $cumulatevalue = $coldummy;
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__(header|result).*__/', $rowId)) {
+                    continue;
+                }
+                $isnullrow = true;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header).*__/', $colId) || $colId == '__resultasset__') {
+                        continue;
+                    }
+                    if ($cumulate) {
+                        $data[$rowId][$colId] += $cumulatevalue[$colId];
+                        $cumulatevalue[$colId] = $data[$rowId][$colId];
+                    }
+                    if ($data[$rowId][$colId] != 0) {
+                        $isnullrow = false;
+                    }
+                }
+                if (!$nullrows && $isnullrow) {
+                    unset($data[$rowId]);
+                }
+            }
+        }
+        
+        /* Sorting. */
+        if ($sortby === null || !isset($colheaders[$sortby])) {
+            $sortby = $this->setParam('sortby', '__header__');
+        }
+        if ($sortdir === null) {
+            $sortdir = $this->setParam('sortdir', FIMA_SORT_ASCEND);
+        }
+        if ($graph) {
+            $sortby = '__header__';
+            $sortdir = FIMA_SORT_ASCEND;
+        }
+
+        $x = -1;
+        $sortIndex = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                $x++;
+                $sortIndex[$x] = array($rowId => $row[$sortby]);
+                $x++;
+            } else {
+                if (!isset($sortIndex[$x])) {
+                    $sortIndex[$x] = array();
+                }
+                $sortIndex[$x][$rowId] = $row[$sortby];
+            }
+        }
+
+        foreach ($sortIndex as $indexId => $indexGroup) {
+            if (count($indexGroup) > 0) {
+                if ($sortdir) {
+                    arsort($indexGroup);
+                } else {
+                    asort($indexGroup);
+                }
+            }
+            foreach ($indexGroup as $rowId => $index) {
+                $this->_data[$rowId] = $data[$rowId];
+            }
+        }
+        
+        return true;
+    }
+    
+   /*
+     * Output the graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _getGraph()
+    {
+        /* Data. */
+        $display = explode('_', $this->getParam('display'));
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $postingtypes = Fima::getPostingTypes();
+
+        $labels = array();
+        $data = $this->_data;
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                foreach ($row as $colId => $value) {
+                    if (!(preg_match('/__(header|result).*__/', $colId) && $colId != '__resulttotal__')) {
+                        $labels[$colId] = $value;
+                    }
+                }
+            }
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                unset($data[$rowId]);
+            } else {
+                $labels[$rowId] = isset($row['__header__']) ? $row['__header__'] : $rowId;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header|result).*__/', $colId) && $colId != '__resulttotal__') {
+                        unset($data[$rowId][$colId]);
+                    }
+                }
+            }
+        }
+        $this->data = $data;
+
+        /* Additional params. */
+        $this->setParam('graph', 'Line');
+        $this->setParam('labels', $labels);
+        
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/fima/lib/Report/Analysis.php b/fima/lib/Report/Analysis.php
new file mode 100644 (file)
index 0000000..127af17
--- /dev/null
@@ -0,0 +1,386 @@
+<?php
+/**
+ * Fima_Report_Analysis.
+ *
+ * $Horde: fima/lib/Report/Analysis.php,v 1.1 2009/03/12 14:30:24 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/Report.php';
+
+/*
+ * Fima_Report_Analysis class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report_Analysis extends Fima_Report {
+
+    /*
+     * Constructs a new Analysis Report.
+     */
+    function Fima_Report_Analysis($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Get account types, posting types and accounts. */
+        $accounttypes = Fima::getAccountTypes();
+        $postingtypes = Fima::getPostingTypes();
+        $accounts = Fima::listAccounts();
+        $accountIndex = array();
+        foreach ($accounts as $accountId => $account) {
+            $accountIndex[$account['number']] = $accountId;
+        }
+        $groups = array(FIMA_ACCOUNTTYPE_INCOME, FIMA_ACCOUNTTYPE_EXPENSE);
+        
+        /* Params. */
+        if (($display = $this->getParam('display')) === null) {
+            return PEAR::raiseError(_("No display type"));
+        }
+        $posting_account = $this->getParam('posting_account');
+        $period_start = $this->getParam('period_start');
+        $period_end   = $this->getParam('period_end');
+        $reference_start = $this->getParam('reference_start');
+        $reference_end   = $this->getParam('reference_end');
+        $subaccounts = $this->getParam('subaccounts');
+        $nullrows = $this->getParam('nullrows');
+        $graph = $this->getParam('graph');
+        $sortby = $this->getParam('sortby');
+        $sortdir = $this->getParam('sortdir');
+
+        /* Rows. */
+        // accounts (dynamically)
+
+        /* Columns. */
+        $cols = explode('_', $display);
+
+        $displaypostingtypes = array();
+        $displayreference = false;
+        $displaydiffa = false;
+        $displaydiffp = false;
+
+        $colheaders = array('__header__' => _("Account"));
+        $coldummy = array();
+        foreach ($cols as $colPos => $colId) {
+            if (isset($postingtypes[$colId])) {
+                $displaypostingtypes[] = $colId;
+                $colheaders[$colId] = $postingtypes[$colId];
+            } elseif ($colId == 'reference') {
+                $displayreference = true;
+                $colheaders[$colId] = _("Reference");
+            } elseif ($colId == 'difference') {
+                $displaydiffa = $colPos;
+                $colheaders[$colId] = _("Difference");
+            } elseif ($colId == '%') {
+                $displaydiffp = $colPos;
+                $colheaders[$colId] = _("Diff. (%)");
+            }
+            $coldummy[$colId] = 0;
+        }
+
+        /* Initialize matrix. */
+        $data = array();
+        $data['__headersort__'] = $colheaders;
+        $datagroups = array(FIMA_ACCOUNTTYPE_INCOME => array(), FIMA_ACCOUNTTYPE_EXPENSE => array());
+        // add parent accounts
+        if ($posting_account) {
+            foreach ($posting_account as $accountId => $account) {
+                if ($accounts[$account]['parent_id']) {
+                    $posting_account[] = $accounts[$account]['parent_id'];
+                }
+            }
+        }
+        foreach ($accounts as $accountId => $account) {
+            if ($posting_account) {
+                if (!in_array($account['account_id'], $posting_account)) {
+                    continue;
+                }
+            }
+            if ($account['type'] == FIMA_ACCOUNTTYPE_ASSET) {
+                continue;
+            }
+            if ($account['parent_id'] === null || !isset($accounts[$account['parent_id']])) {
+                $datagroups[$account['type']][$account['number']] = array('__header__' => $account['label']) + $coldummy;
+                if ($subaccounts) {
+                    $datagroups[$account['type']][$account['number']]['__subaccounts__'] = array();
+                }
+            } elseif ($subaccounts) {
+                $datagroups[$account['type']][$accounts[$account['parent_id']]['number']]['__subaccounts__'][$account['number']] = array('__header__' => ' '.$account['label']) + $coldummy;
+            }
+        }
+        foreach ($datagroups as $datagroupId => $datagroup) {
+            $datagroups[$datagroupId]['__result' . $datagroupId . '__'] = array('__header__' => sprintf(_("%s Result"), $accounttypes[$datagroupId])) + $coldummy;
+            $data += $datagroups[$datagroupId];
+        }
+
+        /* Results. */
+        foreach ($groups as $group) {
+            $filters = array();
+            if ($posting_account) {
+                $filters[] = array('account', $posting_account);
+            }
+            $filters[] = array('account_type', $group);
+            $filters[] = array('type', $displaypostingtypes);
+            if ($period_start !== null) {
+                $filters[] = array('date', (int)$period_start, '>=');
+            }
+            if ($period_end !== null) {
+                $filters[] = array('date', (int)$period_end, '<=');
+            }
+
+            $result = Fima::getResults(array('type', $subaccounts ? 'account_number' : 'account_parent'), $filters);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            foreach ($result as $rowId => $row) {
+                foreach ($row as $colId => $value) {
+                    if (isset($data[$rowId])) {
+                        $data[$rowId][$colId] += $value;
+                    } elseif (($parentId = $accounts[$accountIndex[$rowId]]['parent_id']) !== null) {
+                        if (!$graph) {
+                            $data[$accounts[$parentId]['number']][$colId] += $value;
+                        }
+                        $data[$accounts[$parentId]['number']]['__subaccounts__'][$rowId][$colId] += $value;
+                    }
+                    $data['__result' . $group . '__'][$colId] += $value;
+                }
+            }
+        }
+
+        /* Reference. */
+        if ($displayreference) {
+            foreach ($groups as $group) {
+                $groupresult = array();
+                
+                $filters = array();
+                if ($posting_account) {
+                    $filters[] = array('account', $posting_account);
+                }
+                $filters[] = array('account_type', $group);
+                $filters[] = array('type', $displaypostingtypes);
+                if (($reference_start = $this->getParam('reference_start')) !== null) {
+                    $filters[] = array('date', (int)$reference_start, '>=');
+                }
+                if (($reference_end = $this->getParam('reference_end')) !== null) {
+                    $filters[] = array('date', (int)$reference_end, '<=');
+                }
+            
+                $result = Fima::getResults(array('type', $subaccounts ? 'account_number' : 'account_parent'), $filters);
+                if (is_a($result, 'PEAR_Error')) {
+                    return $result;
+                }
+                foreach ($result as $rowId => $row) {
+                    foreach ($row as $colId => $value) {
+                        $colId = 'reference';
+                        if (isset($data[$rowId])) {
+                            $data[$rowId][$colId] += $value;
+                        } elseif (($parentId = $accounts[$accountIndex[$rowId]]['parent_id']) !== null) {
+                            $data[$accounts[$parentId]['number']][$colId] += $value;
+                            $data[$accounts[$parentId]['number']]['__subaccounts__'][$rowId][$colId] += $value;
+                        }
+                        $data['__result' . $group . '__'][$colId] += $value; 
+                    }
+                }
+            }
+        }
+
+        /* Totals. */
+        $data['__resulttotal__'] = array('__header__' => _("Total Result")) + $coldummy;
+        foreach ($cols as $colId) {
+            foreach ($groups as $groupId => $group) {
+                $data['__resulttotal__'][$colId] += $data['__result' . $group . '__'][$colId];
+            }
+        }
+
+        /* Difference. */
+        if ($displaydiffa > 1 || $displaydiffp > 1) {
+            $cola1 = $cols[$displaydiffa - 2];
+            $cola2 = $cols[$displaydiffa - 1];
+            $colp1 = $cols[$displaydiffp - 2];
+            $colp2 = $cols[$displaydiffp - 1];
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__header.*__/', $rowId)) {
+                    continue;
+                }
+                if ($displaydiffa > 1) {
+                    $data[$rowId]['difference'] = $data[$rowId][$cola1] - $data[$rowId][$cola2];
+                }
+                if ($displaydiffp > 1) {
+                    if ($data[$rowId][$colp1] != 0) {
+                        $data[$rowId]['%'] = $data[$rowId][$colp2] / abs($data[$rowId][$colp1]) * 100;
+                    } else {
+                        $data[$rowId]['%'] = null;
+                    }
+                }
+            }
+        }
+        
+        /* Null Rows. */
+        if (!$nullrows) {
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__(header|result).*__/', $rowId)) {
+                    continue;
+                }
+                $isnullrow = true;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header).*__/', $colId) || $colId == '__resultasset__') {
+                        continue;
+                    }
+                    if ($value != 0) {
+                        $isnullrow = false;
+                    }
+                }
+                if (!$nullrows && $isnullrow) {
+                    unset($data[$rowId]);
+                }
+            }
+        }
+
+        /* Sorting. */
+        if ($sortby === null || !isset($colheaders[$sortby])) {
+            $sortby = $this->setParam('sortby', '__header__');
+        }
+        if ($sortdir === null) {
+            $sortdir = $this->setParam('sortdir', FIMA_SORT_ASCEND);
+        }
+        $x = -1;
+        $sortIndex = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                $x++;
+                $sortIndex[$x] = array($rowId => $row[$sortby]);
+                $x++;
+            } else {
+                if (!isset($sortIndex[$x])) {
+                    $sortIndex[$x] = array();
+                }
+                $sortIndex[$x][$rowId] = $row[$sortby];
+            }
+        }
+
+        foreach ($sortIndex as $indexId => $indexGroup) {
+            if (count($indexGroup) > 0) {
+                if ($sortdir) {
+                    arsort($indexGroup);
+                } else {
+                    asort($indexGroup);
+                }
+            }
+            foreach ($indexGroup as $rowId => $index) {
+                $this->_data[$rowId] = $data[$rowId];
+                if (isset($data[$rowId]['__subaccounts__'])) {
+                    if (count($data[$rowId]['__subaccounts__']) > 0) {
+                        $subSortIndex = array();
+                        foreach ($data[$rowId]['__subaccounts__'] as $subId => $sub) {
+                            $subSortIndex[$subId] = $sub[$sortby];
+                        }
+                        if ($sortdir) {
+                            arsort($subSortIndex);
+                        } else {
+                            asort($subSortIndex);
+                        }
+
+                        foreach ($subSortIndex as $subId => $sub) {
+                            $this->_data[$subId] = $data[$rowId]['__subaccounts__'][$subId];
+                        }
+                    }
+                }
+                unset($this->_data[$rowId]['__subaccounts__']);
+            }
+        }
+        
+        return true;
+    }
+    
+    /*
+     * Output the graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _getGraph()
+    {
+        /* Data. */
+        $display = explode('_', $this->getParam('display'));
+
+        $labels = array();
+        $data = array();
+        $ix = 0;
+        foreach ($this->_data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                foreach ($row as $colId => $value) {
+                    $labels[$colId] = $value;
+                }
+            } elseif (preg_match('/__(result).*__/', $rowId)) {
+                $ix++;
+            } else {
+                $labels[$rowId] = isset($row['__header__']) ? $row['__header__'] : $rowId;
+                if (!isset($data[$ix])) {
+                    $data[$ix] = array();
+                }
+                $data[$ix][$rowId] = $row[$display[0]];
+            }
+        }
+
+        // grouping
+        $sum = array();
+        #for ($i = 0; $i < count($data); $i++) {
+        foreach ($data as $i => $d) {
+            $sum[$i] = array_sum($data[$i]);
+            if ($sum[$i] >= 0) {
+                arsort($data[$i]);
+            } else {
+                asort($data[$i]);
+            }
+            $topdata = array_slice($data[$i], 0, 5, true);
+            $data[$i]['__rest__'] = 0;
+            $data[$i]['__blank__'] = ($sum[$i] == 0) ? 0 : $sum[$i] / abs($sum[$i]);
+            foreach ($data[$i] as $key => $value) {
+                if ((!isset($topdata[$key]) || $value == 0) && $key != '__rest__' && $key != '__blank__') {
+                    $data[$i]['__rest__'] += $value;
+                    unset($data[$i][$key]);
+                }
+            }
+            if ($data[$i]['__rest__'] == 0) {
+                unset($data[$i]['__rest__']);
+            }
+            $sum[$i] = abs($sum[$i]);
+        }
+        $labels['__rest__'] = _("Rest");
+        $labels['__blank__'] = _("Difference");
+
+        // diff
+        $max = max($sum);
+        #for ($i = 0; $i < count($sum); $i++) {
+        foreach ($sum as $i => $s) {
+            if ($max != $sum[$i]) {
+                $data[$i]['__blank__'] *= $max - $sum[$i];
+            } else {
+                unset($data[$i]['__blank__']);
+            }
+        }
+        
+        $this->data = $data;
+
+        /* Additional params. */
+        $this->setParam('graph', 'Pie');
+        $this->setParam('labels', $labels);
+        $this->setParam('subtitle', $labels[($display[0] == 'reference') ? $display[1] : $display[0]]);
+        $this->setParam('marker', true);
+
+        return true;
+    }
+    
+}
\ No newline at end of file
diff --git a/fima/lib/Report/AssetOverview.php b/fima/lib/Report/AssetOverview.php
new file mode 100644 (file)
index 0000000..744cbdd
--- /dev/null
@@ -0,0 +1,327 @@
+<?php
+/**
+ * Fima_Report_AssetOverview.
+ *
+ * $Horde: fima/lib/Report/AssetOverview.php,v 1.0 2008/09/11 19:08:34 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/Report.php';
+
+/*
+ * Fima_Report_AssetOverview class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report_AssetOverview extends Fima_Report {
+
+    /*
+     * Constructs a new AssetOverview Report.
+     */
+    function Fima_Report_AssetOverview($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Get account types, posting types and accounts. */
+        $accounts = Fima::listAccounts(array(array('type', FIMA_ACCOUNTTYPE_ASSET)));
+        $accountIndex = array();
+        foreach ($accounts as $accountId => $account) {
+            $accountIndex[$account['number']] = $accountId;
+        }
+        
+        /* Params. */
+        if (($display = $this->getParam('display')) === null) {
+            return PEAR::raiseError(_("No display type"));
+        }
+        $display = explode('_', $display);
+        $period_start = ($display[0] == 'reference') ? $this->getParam('reference_start') : $this->getParam('period_start');
+        $period_end   = ($display[0] == 'reference') ? $this->getParam('reference_end')   : $this->getParam('period_end');
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $cumulate = $this->getParam('cumulate');
+        $nullrows = $this->getParam('nullrows');
+        $subaccounts = $this->getParam('subaccounts');
+        $graph = $this->getParam('graph');
+        $yearly = $this->getParam('yearly');
+        $datefmt = $yearly ? '%Y' : Fima::convertDateToPeriodFormat($GLOBALS['prefs']->getValue('date_format'));
+        $sortby = $this->getParam('sortby');
+        $sortdir = $this->getParam('sortdir');
+
+        /* Rows. */
+        // accounts (dynamically)
+
+        /* Columns. */
+        $cols = array();
+        $colheaders = array('__header__' => _("Asset"));
+        $coldummy = array();
+        for ($period = $period_start; $period <= $period_end; $period = strtotime($yearly ? '+1 year' : '+1 month', $period)) {
+            $colId = strftime($yearly ? '%Y' : '%Y%m', $period);
+            $cols[] = $colId;
+            $colheaders[$colId] = strftime($datefmt, $period);
+            $coldummy[$colId] = 0;
+        }
+        $colheaders['__result__'] = _("Total Result");
+        $coldummy['__result__'] = 0;
+        
+        /* Initialize matrix. */
+        $data = array();
+        $data['__headersort__'] = $colheaders;
+        foreach ($accounts as $accountId => $account) {
+            if ($account['parent_id'] === null || !isset($accounts[$account['parent_id']])) {
+                $data[$account['number']] = array('__header__' => $account['label']) + $coldummy;
+                if ($subaccounts) {
+                    $data[$account['number']]['__subaccounts__'] = array();
+                }
+            } elseif ($subaccounts) {
+                $data[$accounts[$account['parent_id']]['number']]['__subaccounts__'][$account['number']] = array('__header__' => ' '.$account['label']) + $coldummy;
+            }
+        }
+        $data['__result__'] = array('__header__' => _("Asset Result")) + $coldummy;
+
+        /* Initialize asset results. */
+        if ($cumulate) {
+            $assetresults = Fima::getAssetResults($display, $period_start - 1);
+            $period = strftime($yearly ? '%Y' : '%Y%m', $period_start);
+            foreach ($assetresults as $assetresult) {
+                if (isset($data[$accounts[$assetresult['account_id']]['number']])) {
+                    $data[$accounts[$assetresult['account_id']]['number']][$period] += $assetresult['account_result'];
+                    $data[$accounts[$assetresult['account_id']]['number']]['__result__'] += $assetresult['account_result'];
+                } elseif (($parentId = $accounts[$assetresult['account_id']]['parent_id']) !== null) {
+                    $data[$accounts[$parentId]['number']][$period] += $assetresult['account_result'];
+                    $data[$accounts[$parentId]['number']]['__result__'] += $assetresult['account_result'];
+                    if ($subaccounts) {
+                        $data[$accounts[$parentId]['number']]['__subaccounts__'][$accounts[$assetresult['account_id']]['number']][$period] += $assetresult['account_result'];
+                        $data[$accounts[$parentId]['number']]['__subaccounts__'][$accounts[$assetresult['account_id']]['number']]['__result__'] += $assetresult['account_result'];
+                    }
+                }
+                $data['__result__'][$period] += $assetresult['account_result'];
+                $data['__result__']['__result__'] += $assetresult['account_result'];
+            }
+        }
+
+        /* Results. */
+        $filters = array();
+        $filters[] = array('type', $display);
+        if ($period_start !== null) {
+            $filters[] = array('date', (int)$period_start, '>=');
+        }
+        if ($period_end !== null) {
+            $filters[] = array('date', (int)$period_end, '<=');
+        }
+
+        // asset accounts
+        $result = Fima::getResults(array($yearly ? 'date_year' : 'date_month', $subaccounts ? 'asset_number' : 'asset_parent'), $filters);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        // asset accounts as posting account
+        $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET);
+        $result2 = Fima::getResults(array($yearly ? 'date_year' : 'date_month', $subaccounts ? 'account_number' : 'account_parent'), $filters);
+        if (is_a($result2, 'PEAR_Error')) {
+            return $result2;
+        }
+        foreach ($result2 as $rowId => $row) {
+            if (!isset($result[$rowId])) {
+                $result[$rowId] = array();
+            }
+            foreach ($row as $colId => $value) {
+                if (!isset($result[$rowId][$colId])) {
+                    $result[$rowId][$colId] = 0;
+                }
+                $result[$rowId][$colId] -= $value;
+            }
+        }
+
+        foreach ($result as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                if (isset($data[$rowId])) {
+                    $data[$rowId][$colId] += $value;
+                    $data[$rowId]['__result__'] += $value;
+                } elseif (($parentId = $accounts[$accountIndex[$rowId]]['parent_id']) !== null) {
+                    $data[$accounts[$parentId]['number']][$colId] += $value;
+                    $data[$accounts[$parentId]['number']]['__result__'] += $value;
+                    if ($subaccounts) {
+                        $data[$accounts[$parentId]['number']]['__subaccounts__'][$rowId][$colId] += $value;
+                        $data[$accounts[$parentId]['number']]['__subaccounts__'][$rowId]['__result__'] += $value;
+                    }
+                }
+                $data['__result__'][$colId] += $value;
+                $data['__result__']['__result__'] += $value;
+            }
+        }
+
+        /* Null Rows and Cumulate. */
+        if (!$nullrows || $cumulate) {
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__(header).*__/', $rowId)) {
+                    continue;
+                }
+                $cumulatevalue = 0;
+                $isnullrow = true;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header|result).*__/', $colId)) {
+                        continue;
+                    }
+                    if ($colId == '__subaccounts__') {
+                        if (count($value) > 0) {
+                            foreach ($value as $subRowId => $subRow) {
+                                $subcumulatevalue = 0;
+                                $subisnullrow = true;
+                                foreach ($subRow as $subColId => $subValue) {
+                                    if (preg_match('/__(header|result).*__/', $subColId)) {
+                                        continue;
+                                    }
+                                    if ($cumulate) {
+                                        $data[$rowId]['__subaccounts__'][$subRowId][$subColId] += $subcumulatevalue;
+                                        $subcumulatevalue = $data[$rowId]['__subaccounts__'][$subRowId][$subColId];
+                                    }
+                                    if ($data[$rowId]['__subaccounts__'][$subRowId][$subColId] != 0) {
+                                        $subisnullrow = false;
+                                    }
+                                }
+                                if (!$nullrows && $subisnullrow) {
+                                    unset($data[$rowId]['__subaccounts__'][$subRowId]);
+                                }
+                            }
+                        }
+                    } else {
+                        if ($cumulate) {
+                            $data[$rowId][$colId] += $cumulatevalue;
+                            $cumulatevalue = $data[$rowId][$colId];
+                        }
+                        if ($data[$rowId][$colId] != 0) {
+                            $isnullrow = false;
+                        }
+                    }
+                }
+                if (!$nullrows && $isnullrow) {
+                    unset($data[$rowId]);
+                }
+            }
+        }
+        
+        /* Sorting. */
+        if ($sortby === null || !isset($colheaders[$sortby])) {
+            $sortby = $this->setParam('sortby', '__header__');
+        }
+        if ($sortdir === null) {
+            $sortdir = $this->setParam('sortdir', FIMA_SORT_ASCEND);
+        }
+        $x = -1;
+        $sortIndex = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                $x++;
+                $sortIndex[$x] = array($rowId => $row[$sortby]);
+                $x++;
+            } else {
+                if (!isset($sortIndex[$x])) {
+                    $sortIndex[$x] = array();
+                }
+                $sortIndex[$x][$rowId] = $row[$sortby];
+            }
+        }
+
+        foreach ($sortIndex as $indexId => $indexGroup) {
+            if (count($indexGroup) > 0) {
+                if ($sortdir) {
+                    arsort($indexGroup);
+                } else {
+                    asort($indexGroup);
+                }
+            }
+            foreach ($indexGroup as $rowId => $index) {
+                $this->_data[$rowId] = $data[$rowId];
+                if (isset($data[$rowId]['__subaccounts__'])) {
+                    if (count($data[$rowId]['__subaccounts__']) > 0) {
+                        $subSortIndex = array();
+                        foreach ($data[$rowId]['__subaccounts__'] as $subId => $sub) {
+                            $subSortIndex[$subId] = $sub[$sortby];
+                        }
+                        if ($sortdir) {
+                            arsort($subSortIndex);
+                        } else {
+                            asort($subSortIndex);
+                        }
+
+                        foreach ($subSortIndex as $subId => $sub) {
+                            $this->_data[$subId] = $data[$rowId]['__subaccounts__'][$subId];
+                        }
+                    }
+                }
+                unset($this->_data[$rowId]['__subaccounts__']);
+            }
+        }
+        
+        return true;
+    }
+    
+    /*
+     * Output the graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _getGraph()
+    {
+        /* Data. */
+        $display = explode('_', $this->getParam('display'));
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $postingtypes = Fima::getPostingTypes();
+
+        $labels = array();
+        $data = $this->_data;
+        $sum = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                foreach ($row as $colId => $value) {
+                    if (!(preg_match('/__(header|result).*__/', $colId) && $colId != '__resulttotal__')) {
+                        $labels[$colId] = $value;
+                    }
+                }
+            }
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                unset($data[$rowId]);
+            } else {
+                $labels[$rowId] = isset($row['__header__']) ? $row['__header__'] : $rowId;
+                if ($data[$rowId]['__result__'] != 0) {
+                    $sum[$rowId] = $data[$rowId]['__result__'];
+                }
+                unset($data[$rowId]['__header__']);
+                unset($data[$rowId]['__result__']);
+            }
+        }
+
+        // grouping
+        asort($sum);
+        $topdata = array_slice($sum, 0, 5, true) + array_slice($sum, -5, 5, true);
+        foreach ($data as $rowId => $row) {
+            if (!isset($topdata[$rowId])) {
+                unset($data[$rowId]);
+            }
+        }
+
+        $this->data = $data;
+
+        /* Additional params. */
+        $this->setParam('graph', 'Line');
+        $this->setParam('labels', $labels);
+        $this->setParam('subtitle', $postingtypes[$display]);
+        $this->setParam('invert', true);
+
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/fima/lib/Report/GeneralOverview.php b/fima/lib/Report/GeneralOverview.php
new file mode 100644 (file)
index 0000000..111861c
--- /dev/null
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Fima_Report_GeneralOverview.
+ *
+ * $Horde: fima/lib/Report/GeneralOverview.php,v 1.0 2008/06/23 21:15:08 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/Report.php';
+
+/*
+ * Fima_Report_GeneralOverview class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report_GeneralOverview extends Fima_Report {
+
+    /*
+     * Constructs a new GeneralOverview Report.
+     */
+    function Fima_Report_GeneralOverview($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Get account types and posting types. */
+        $accounttypes = Fima::getAccountTypes();
+        $postingtypes = Fima::getPostingTypes();
+        
+        /* Params. */
+        if (($display = $this->getParam('display')) === null) {
+            return PEAR::raiseError(_("No display type"));
+        }
+        $posting_account = $this->getParam('posting_account');
+        $period_start = $this->getParam('period_start');
+        $period_end   = $this->getParam('period_end');
+        $reference_start = $this->getParam('reference_start');
+        $reference_end   = $this->getParam('reference_end');
+
+        /* Rows. */
+        $rows = array(FIMA_ACCOUNTTYPE_INCOME, FIMA_ACCOUNTTYPE_EXPENSE);
+        $groups = array('0' => "%s", '1' => "e.o. %s", 'total' => "Total %s");
+        $rowheaders = array();
+        foreach ($groups as $groupId => $group) {
+            foreach ($rows as $rowPos => $rowId) {
+                $rowheaders[$rowId . $groupId] = sprintf(_($group), $accounttypes[$rowId]);
+            }
+            $rowheaders['__result' . $groupId . '__'] = sprintf(_($group), _("Result"));
+        }
+        $rowheaders['__resultasset__'] = _("Asset Result");
+        
+        /* Columns. */
+        $cols = explode('_', $display);
+
+        $displaypostingtypes = array();
+        $displayreference = false;
+        $displaydiffa = false;
+        $displaydiffp = false;
+
+        $colheaders = array('__header__' => _("Type"));
+        $coldummy = array();
+        foreach ($cols as $colPos => $colId) {
+            if (isset($postingtypes[$colId])) {
+                $displaypostingtypes[] = $colId;
+                $colheaders[$colId] = $postingtypes[$colId];
+            } elseif ($colId == 'reference') {
+                $displayreference = true;
+                $colheaders[$colId] = _("Reference");
+            } elseif ($colId == 'difference') {
+                $displaydiffa = $colPos;
+                $colheaders[$colId] = _("Difference");
+            } elseif ($colId == '%') {
+                $displaydiffp = $colPos;
+                $colheaders[$colId] = _("Diff. (%)");
+            }
+            $coldummy[$colId] = 0;
+        }
+        
+        /* Initialize matrix. */
+        $data = array();
+        $data['__header__'] = $colheaders;
+        foreach ($rowheaders as $rowId => $rowheader) {
+            $data[$rowId] = array('__header__' => $rowheader) + $coldummy;;
+        }
+        $assetresult = array('__header__' => _("Asset Result")) + $coldummy;
+
+        /* Results. */
+        $groups = array('0', '1');
+        foreach ($groups as $group) {
+            $filters = array();
+            if ($posting_account) {
+                $filters[] = array('account', $posting_account);
+            }
+            $filters[] = array('account_type', $rows);
+            $filters[] = array('type', $displaypostingtypes);
+            $filters[] = array('eo', $group);
+            if ($period_start !== null) {
+                $filters[] = array('date', (int)$period_start, '>=');
+            }
+            if ($period_end !== null) {
+                $filters[] = array('date', (int)$period_end, '<=');
+            }
+
+            $result = Fima::getResults(array('type', 'account_type'), $filters);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            foreach ($result as $rowId => $row) {
+                foreach ($row as $colId => $value) {
+                    $data[$rowId . $group][$colId] = $value;
+                    $data['__result' . $group . '__'][$colId] += $value;
+                }
+            }
+        }
+
+        // asset results
+        $filters = array();
+        $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET, '<>');
+        $filters[] = array('type', $displaypostingtypes);
+        if ($period_end !== null) {
+            $filters[] = array('date', (int)$period_end, '<');
+        }
+
+        $result = Fima::getResults('type', $filters);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        foreach ($result as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $data['__resultasset__'][$colId] = $value;
+            }
+        }
+
+        /* Reference. */
+        if ($displayreference) {
+            $groups = array('0', '1');
+            foreach ($groups as $group) {
+                $filters = array();
+                if ($posting_account) {
+                    $filters[] = array('account', $posting_account);
+                }
+                $filters[] = array('account_type', $rows);
+                $filters[] = array('type', $displaypostingtypes[0]);
+                $filters[] = array('eo', $group);
+                if ($reference_start !== null) {
+                    $filters[] = array('date', (int)$reference_start, '>=');
+                }
+                if ($reference_end !== null) {
+                    $filters[] = array('date', (int)$reference_end, '<=');
+                }
+            
+                $result = Fima::getResults(array('type', 'account_type'), $filters);
+                if (is_a($result, 'PEAR_Error')) {
+                    return $result;
+                }
+                foreach ($result as $rowId => $row) {
+                    foreach ($row as $colId => $value) {
+                        $colId = 'reference';
+                        $data[$rowId . $group][$colId] = $value;
+                        $data['__result' . $group . '__'][$colId] += $value; 
+                    }
+                }
+            }
+
+            // asset results
+            $filters = array();
+            $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET, '<>');
+            $filters[] = array('type', $displaypostingtypes[0]);
+            if ($reference_end !== null) {
+                $filters[] = array('date', (int)$reference_end, '<');
+            }
+
+            $result = Fima::getResults('type', $filters);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            foreach ($result as $rowId => $row) {
+                foreach ($row as $colId => $value) {
+                    $data['__resultasset__']['reference'] = $value;
+                }
+            }
+        }
+        
+        /* Totals. */
+        foreach ($cols as $colId) {
+            foreach ($rows as $rowId) {
+                $data[$rowId . 'total'][$colId] = $data[$rowId . '0'][$colId] + $data[$rowId . '1'][$colId];
+            }
+            $data['__resulttotal__'][$colId] = $data['__result0__'][$colId] + $data['__result1__'][$colId];
+        }
+
+        /* Difference. */
+        if ($displaydiffa > 1 || $displaydiffp > 1) {
+            $cola1 = $cols[$displaydiffa - 2];
+            $cola2 = $cols[$displaydiffa - 1];
+            $colp1 = $cols[$displaydiffp - 2];
+            $colp2 = $cols[$displaydiffp - 1];
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__header.*__/', $rowId)) {
+                    continue;
+                }
+                if ($displaydiffa > 1) {
+                    $data[$rowId]['difference'] = $data[$rowId][$cola1] - $data[$rowId][$cola2];
+                }
+                if ($displaydiffp > 1) {
+                    if ($data[$rowId][$colp1] != 0) {
+                        $data[$rowId]['%'] = $data[$rowId][$colp2] / abs($data[$rowId][$colp1]) * 100;
+                    } else {
+                        $data[$rowId]['%'] = null;
+                    }
+                }
+            }
+        }
+        
+        $this->_data = $data;
+        
+        return true;
+    }
+
+    /*
+     * Output the graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _getGraph()
+    {
+        /* Data. */
+        $labels = array();
+        $data = $this->_data;
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                foreach ($row as $colId => $value) {
+                    if (!preg_match('/__(header|result).*__/', $colId)) {
+                        $labels[$colId] = $value;
+                    }
+                }
+            }
+            if (preg_match('/__(header|result).*__/', $rowId) && $rowId != '__resulttotal__') {
+                unset($data[$rowId]);
+            } else {
+                $labels[$rowId] = isset($row['__header__']) ? $row['__header__'] : $rowId;
+                unset($data[$rowId]['__header__']);
+            }
+        }
+        $this->data = $data;
+
+        /* Additional params. */
+        $this->setParam('graph', 'Bar');
+        $this->setParam('stacked', 'false');
+        $this->setParam('labels', $labels);
+
+        return true;
+    }
+    
+}
\ No newline at end of file
diff --git a/fima/lib/Report/PeriodOverview.php b/fima/lib/Report/PeriodOverview.php
new file mode 100644 (file)
index 0000000..95dbb18
--- /dev/null
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Fima_Report_PeriodOverview.
+ *
+ * $Horde: fima/lib/Report/PeriodOverview.php,v 1.1 2009/03/13 17:45:08 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/Report.php';
+
+/*
+ * Fima_Report_PeriodOverview class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report_PeriodOverview extends Fima_Report {
+
+    /*
+     * Constructs a new PeriodOverview Report.
+     */
+    function Fima_Report_PeriodOverview($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Get account types. */
+        $accounttypes = Fima::getAccountTypes();
+
+        /* Params. */
+        if (($display = $this->getParam('display')) === null) {
+            return PEAR::raiseError(_("No display type"));
+        }
+        $display = explode('_', $display);
+        $posting_account = $this->getParam('posting_account');
+        $period_start = ($display[0] == 'reference') ? $this->getParam('reference_start') : $this->getParam('period_start');
+        $period_end   = ($display[0] == 'reference') ? $this->getParam('reference_end')   : $this->getParam('period_end');
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $cumulate = $this->getParam('cumulate');
+        $nullrows = $this->getParam('nullrows');
+        $yearly = $this->getParam('yearly');
+        $graph = $this->getParam('graph');
+        $datefmt = $yearly ? '%Y' : Fima::convertDateToPeriodFormat($GLOBALS['prefs']->getValue('date_format'));
+        $sortby = $this->getParam('sortby');
+        $sortdir = $this->getParam('sortdir');
+
+        /* Rows. */
+        $rows = array();
+        for ($period = $period_start; $period <= $period_end; $period = strtotime($yearly ? '+1 year' : '+1 month', $period)) {
+            $rows[strftime($yearly ? '%Y' : '%Y%m', $period)] = strftime($datefmt, $period);
+        }
+
+        /* Columns. */
+        $cols = array(FIMA_ACCOUNTTYPE_INCOME, FIMA_ACCOUNTTYPE_EXPENSE);
+        $colheaders = array('__header__' => _("Period"));
+        $coldummy = array();
+        $groups = array('0' => "%s", '1' => "e.o. %s", 'total' => "Total %s");
+        foreach ($groups as $groupId => $group) {
+            foreach ($cols as $colPos => $colId) {
+                $coldummy[$colId . $groupId] = 0;
+                $colheaders[$colId . $groupId] = sprintf($group, $accounttypes[$colId]);
+            }
+            $coldummy['__result' . $groupId . '__'] = 0;
+            $colheaders['__result' . $groupId . '__'] = sprintf($group, _("Result"));
+        }
+        $colheaders['__resultasset__'] = _("Asset Result");
+        $coldummy['__resultasset__'] = 0;
+
+        /* Initialize matrix. */
+        $data = array();
+        $data['__headersort__'] = $colheaders;
+        foreach ($rows as $rowId => $rowLabel) {
+            $data[$rowId] = array('__header__' => $rowLabel) + $coldummy;
+        }
+
+        /* Results. */
+        $groups = array('0', '1');
+        $total = array('__header__' => _("Total Result")) + $coldummy;
+        foreach ($groups as $group) {
+            $filters = array();
+            if ($posting_account) {
+                $filters[] = array('account', $posting_account);
+            }
+            $filters[] = array('account_type', $cols);
+            $filters[] = array('type', $display);
+            $filters[] = array('eo', $group);
+            if ($period_start !== null) {
+                $filters[] = array('date', (int)$period_start, '>=');
+            }
+            if ($period_end !== null) {
+                $filters[] = array('date', (int)$period_end, '<=');
+            }
+
+            $result = Fima::getResults(array('account_type', $yearly ? 'date_year' : 'date_month'), $filters);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            foreach ($result as $rowId => $row) {
+                foreach ($row as $colId => $value) {
+                    $data[$rowId][$colId . $group] = $value;
+                    $data[$rowId]['__result' . $group . '__'] += $value;
+                    $total[$colId . $group] += $value;
+                    $total['__result' . $group . '__'] += $value;
+                }
+            }
+        }
+
+        /* Totals. */
+        $data['__resulttotal__'] = $total;
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                continue;
+            }
+            foreach ($cols as $colId) {
+                $data[$rowId][$colId . 'total'] = $data[$rowId][$colId . '0'] + $data[$rowId][$colId . '1'];
+                
+            }
+            $data[$rowId]['__resulttotal__'] = $data[$rowId]['__result0__'] + $data[$rowId]['__result1__'];
+        }
+        
+         /* Asset Results. */
+        $filters = array();
+        $filters[] = array('account_type', FIMA_ACCOUNTTYPE_ASSET, '<>');
+        $filters[] = array('type', $display);
+        if ($period_start !== null) {
+            $filters[] = array('date', (int)$period_start, '<');
+        }
+
+        $result = Fima::getResults(array('type'), $filters);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $assetresult = 0;
+        foreach ($result as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $assetresult += $value;
+            }
+        }
+        
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                continue;
+            }
+            if (!preg_match('/__result.*__/', $rowId)) {
+                $assetresult += $data[$rowId]['__resulttotal__'];
+            }
+            $data[$rowId]['__resultasset__'] = $assetresult;
+        }
+
+        /* Null Rows and Cumulate. */
+        if (!$nullrows || $cumulate) {
+            $cumulatevalue = $coldummy;
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__(header|result).*__/', $rowId)) {
+                    continue;
+                }
+                $isnullrow = true;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header).*__/', $colId) || $colId == '__resultasset__') {
+                        continue;
+                    }
+                    if ($cumulate) {
+                        $data[$rowId][$colId] += $cumulatevalue[$colId];
+                        $cumulatevalue[$colId] = $data[$rowId][$colId];
+                    }
+                    if ($data[$rowId][$colId] != 0) {
+                        $isnullrow = false;
+                    }
+                }
+                if (!$nullrows && $isnullrow) {
+                    unset($data[$rowId]);
+                }
+            }
+        }
+        
+        /* Sorting. */
+        if ($sortby === null || !isset($colheaders[$sortby])) {
+            $sortby = $this->setParam('sortby', '__header__');
+        }
+        if ($sortdir === null) {
+            $sortdir = $this->setParam('sortdir', FIMA_SORT_ASCEND);
+        }
+        if ($graph) {
+            $sortby = '__header__';
+            $sortdir = FIMA_SORT_ASCEND;
+        }
+
+        $x = -1;
+        $sortIndex = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                $x++;
+                $sortIndex[$x] = array($rowId => $row[$sortby]);
+                $x++;
+            } else {
+                if (!isset($sortIndex[$x])) {
+                    $sortIndex[$x] = array();
+                }
+                $sortIndex[$x][$rowId] = $row[$sortby];
+            }
+        }
+
+        foreach ($sortIndex as $indexId => $indexGroup) {
+            if (count($indexGroup) > 0) {
+                if ($sortdir) {
+                    arsort($indexGroup);
+                } else {
+                    asort($indexGroup);
+                }
+            }
+            foreach ($indexGroup as $rowId => $index) {
+                $this->_data[$rowId] = $data[$rowId];
+            }
+        }
+        
+        return true;
+    }
+    
+   /*
+     * Output the graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _getGraph()
+    {
+        /* Data. */
+        $display = explode('_', $this->getParam('display'));
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $postingtypes = Fima::getPostingTypes();
+
+        $labels = array();
+        $data = $this->_data;
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                foreach ($row as $colId => $value) {
+                    if (!(preg_match('/__(header|result).*__/', $colId) && $colId != '__resulttotal__')) {
+                        $labels[$colId] = $value;
+                    }
+                }
+            }
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                unset($data[$rowId]);
+            } else {
+                $labels[$rowId] = isset($row['__header__']) ? $row['__header__'] : $rowId;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header|result).*__/', $colId) && $colId != '__resulttotal__') {
+                        unset($data[$rowId][$colId]);
+                    }
+                }
+            }
+        }
+        $this->data = $data;
+
+        /* Additional params. */
+        $this->setParam('graph', 'Line');
+        $this->setParam('labels', $labels);
+        $this->setParam('subtitle', $postingtypes[$display]);
+        
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/fima/lib/Report/Trend.php b/fima/lib/Report/Trend.php
new file mode 100644 (file)
index 0000000..eae8114
--- /dev/null
@@ -0,0 +1,325 @@
+<?php
+/**
+ * Fima_Report_Trend.
+ *
+ * $Horde: fima/lib/Report/Trend.php,v 1.1 2009/03/13 17:59:56 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/Report.php';
+
+/*
+ * Fima_Report_Trend class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_Report_Trend extends Fima_Report {
+
+    /*
+     * Constructs a new Trend Report.
+     */
+    function Fima_Report_Trend($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Get account types, posting types and accounts. */
+        $accounttypes = Fima::getAccountTypes();
+        $accounts = Fima::listAccounts();
+        $accountIndex = array();
+        foreach ($accounts as $accountId => $account) {
+            $accountIndex[$account['number']] = $accountId;
+        }
+        $groups = array(FIMA_ACCOUNTTYPE_INCOME, FIMA_ACCOUNTTYPE_EXPENSE);
+        
+        /* Params. */
+        if (($display = $this->getParam('display')) === null) {
+            return PEAR::raiseError(_("No display type"));
+        }
+        $display = explode('_', $display);
+        $posting_account = $this->getParam('posting_account');
+        $period_start = ($display[0] == 'reference') ? $this->getParam('reference_start') : $this->getParam('period_start');
+        $period_end   = ($display[0] == 'reference') ? $this->getParam('reference_end')   : $this->getParam('period_end');
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $cumulate = $this->getParam('cumulate');
+        $nullrows = $this->getParam('nullrows');
+        $subaccounts = $this->getParam('subaccounts');
+        $graph = $this->getParam('graph');
+        $yearly = $this->getParam('yearly');
+        $datefmt = $yearly ? '%Y' : Fima::convertDateToPeriodFormat($GLOBALS['prefs']->getValue('date_format'));
+        $sortby = $this->getParam('sortby');
+        $sortdir = $this->getParam('sortdir');
+
+        /* Rows. */
+        // accounts (dynamically)
+
+        /* Columns. */
+        $cols = array();
+        $colheaders = array('__header__' => _("Account"));
+        $coldummy = array();
+        for ($period = $period_start; $period <= $period_end; $period = strtotime($yearly ? '+1 year' : '+1 month', $period)) {
+            $colId = strftime($yearly ? '%Y' : '%Y%m', $period);
+            $cols[] = $colId;
+            $colheaders[$colId] = strftime($datefmt, $period);
+            $coldummy[$colId] = 0;
+        }
+        $colheaders['__result__'] = _("Total Result");
+        $coldummy['__result__'] = 0;
+        
+        /* Initialize matrix. */
+        $data = array();
+        $data['__headersort__'] = $colheaders;
+        $datagroups = array(FIMA_ACCOUNTTYPE_INCOME => array(), FIMA_ACCOUNTTYPE_EXPENSE => array());
+        // add parent accounts
+        if ($posting_account) {
+            foreach ($posting_account as $accountId => $account) {
+                if ($accounts[$account]['parent_id']) {
+                    $posting_account[] = $accounts[$account]['parent_id'];
+                }
+            }
+        }
+        foreach ($accounts as $accountId => $account) {
+            if ($posting_account) {
+                if (!in_array($account['account_id'], $posting_account)) {
+                    continue;
+                }
+            }
+            if ($account['type'] == FIMA_ACCOUNTTYPE_ASSET) {
+                continue;
+            }
+            if ($account['parent_id'] === null || !isset($accounts[$account['parent_id']])) {
+                $datagroups[$account['type']][$account['number']] = array('__header__' => $account['label']) + $coldummy;
+                if ($subaccounts) {
+                    $datagroups[$account['type']][$account['number']]['__subaccounts__'] = array();
+                }
+            } elseif ($subaccounts) {
+                $datagroups[$account['type']][$accounts[$account['parent_id']]['number']]['__subaccounts__'][$account['number']] = array('__header__' => ' '.$account['label']) + $coldummy;
+            }
+        }
+        foreach ($datagroups as $datagroupId => $datagroup) {
+            $datagroups[$datagroupId]['__result' . $datagroupId . '__'] = array('__header__' => sprintf(_("%s Result"), $accounttypes[$datagroupId])) + $coldummy;
+            $data += $datagroups[$datagroupId];
+        }
+
+        /* Results. */
+        foreach ($groups as $group) {
+            $filters = array();
+            if ($posting_account) {
+                $filters[] = array('account', $posting_account);
+            }
+            $filters[] = array('account_type', $group);
+            $filters[] = array('type', $display);
+            if ($period_start !== null) {
+                $filters[] = array('date', (int)$period_start, '>=');
+            }
+            if ($period_end !== null) {
+                $filters[] = array('date', (int)$period_end, '<=');
+            }
+
+            $result = Fima::getResults(array($yearly ? 'date_year' : 'date_month', $subaccounts ? 'account_number' : 'account_parent'), $filters);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            foreach ($result as $rowId => $row) {
+                foreach ($row as $colId => $value) {
+                    if (isset($data[$rowId])) {
+                        $data[$rowId][$colId] += $value;
+                        $data[$rowId]['__result__'] += $value;
+                    } elseif (($parentId = $accounts[$accountIndex[$rowId]]['parent_id']) !== null) {
+                        $data[$accounts[$parentId]['number']][$colId] += $value;
+                        $data[$accounts[$parentId]['number']]['__result__'] += $value;
+                        if ($subaccounts) {
+                            $data[$accounts[$parentId]['number']]['__subaccounts__'][$rowId][$colId] += $value;
+                            $data[$accounts[$parentId]['number']]['__subaccounts__'][$rowId]['__result__'] += $value;
+                        }
+                    }
+                    $data['__result' . $group . '__'][$colId] += $value;
+                    $data['__result' . $group . '__']['__result__'] += $value;
+                }
+            }
+        }
+
+        /* Totals. */
+        $data['__resulttotal__'] = array('__header__' => _("Total Result")) + $coldummy;
+        foreach ($cols as $colId) {
+            foreach ($groups as $groupId => $group) {
+                $data['__resulttotal__'][$colId] += $data['__result' . $group . '__'][$colId];
+                $data['__resulttotal__']['__result__'] += $data['__result' . $group . '__'][$colId];
+            }
+        }
+
+        /* Null Rows and Cumulate. */
+        if (!$nullrows || $cumulate) {
+            foreach ($data as $rowId => $row) {
+                if (preg_match('/__(header).*__/', $rowId)) {
+                    continue;
+                }
+                $cumulatevalue = 0;
+                $isnullrow = true;
+                foreach ($row as $colId => $value) {
+                    if (preg_match('/__(header|result).*__/', $colId)) {
+                        continue;
+                    }
+                    if ($colId == '__subaccounts__') {
+                        if (count($value) > 0) {
+                            foreach ($value as $subRowId => $subRow) {
+                                $subcumulatevalue = 0;
+                                $subisnullrow = true;
+                                foreach ($subRow as $subColId => $subValue) {
+                                    if (preg_match('/__(header|result).*__/', $subColId)) {
+                                        continue;
+                                    }
+                                    if ($cumulate) {
+                                        $data[$rowId]['__subaccounts__'][$subRowId][$subColId] += $subcumulatevalue;
+                                        $subcumulatevalue = $data[$rowId]['__subaccounts__'][$subRowId][$subColId];
+                                    }
+                                    if ($data[$rowId]['__subaccounts__'][$subRowId][$subColId] != 0) {
+                                        $subisnullrow = false;
+                                    }
+                                }
+                                if (!$nullrows && $subisnullrow) {
+                                    unset($data[$rowId]['__subaccounts__'][$subRowId]);
+                                }
+                            }
+                        }
+                    } else {
+                        if ($cumulate) {
+                            $data[$rowId][$colId] += $cumulatevalue;
+                            $cumulatevalue = $data[$rowId][$colId];
+                        }
+                        if ($data[$rowId][$colId] != 0) {
+                            $isnullrow = false;
+                        }
+                    }
+                }
+                if (!$nullrows && $isnullrow) {
+                    unset($data[$rowId]);
+                }
+            }
+        }
+        
+        /* Sorting. */
+        if ($sortby === null || !isset($colheaders[$sortby])) {
+            $sortby = $this->setParam('sortby', '__header__');
+        }
+        if ($sortdir === null) {
+            $sortdir = $this->setParam('sortdir', FIMA_SORT_ASCEND);
+        }
+        $x = -1;
+        $sortIndex = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                $x++;
+                $sortIndex[$x] = array($rowId => $row[$sortby]);
+                $x++;
+            } else {
+                if (!isset($sortIndex[$x])) {
+                    $sortIndex[$x] = array();
+                }
+                $sortIndex[$x][$rowId] = $row[$sortby];
+            }
+        }
+
+        foreach ($sortIndex as $indexId => $indexGroup) {
+            if (count($indexGroup) > 0) {
+                if ($sortdir) {
+                    arsort($indexGroup);
+                } else {
+                    asort($indexGroup);
+                }
+            }
+            foreach ($indexGroup as $rowId => $index) {
+                $this->_data[$rowId] = $data[$rowId];
+                if (isset($data[$rowId]['__subaccounts__'])) {
+                    if (count($data[$rowId]['__subaccounts__']) > 0) {
+                        $subSortIndex = array();
+                        foreach ($data[$rowId]['__subaccounts__'] as $subId => $sub) {
+                            $subSortIndex[$subId] = $sub[$sortby];
+                        }
+                        if ($sortdir) {
+                            arsort($subSortIndex);
+                        } else {
+                            asort($subSortIndex);
+                        }
+
+                        foreach ($subSortIndex as $subId => $sub) {
+                            $this->_data[$subId] = $data[$rowId]['__subaccounts__'][$subId];
+                        }
+                    }
+                }
+                unset($this->_data[$rowId]['__subaccounts__']);
+            }
+        }
+        
+        return true;
+    }
+    
+    /*
+     * Output the graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _getGraph()
+    {
+        /* Data. */
+        $display = explode('_', $this->getParam('display'));
+        $display = ($display[0] == 'reference') ? $display[1] : $display[0];
+        $postingtypes = Fima::getPostingTypes();
+
+        $labels = array();
+        $data = $this->_data;
+        $sum = array();
+        foreach ($data as $rowId => $row) {
+            if (preg_match('/__header.*__/', $rowId)) {
+                foreach ($row as $colId => $value) {
+                    if (!(preg_match('/__(header|result).*__/', $colId) && $colId != '__resulttotal__')) {
+                        $labels[$colId] = $value;
+                    }
+                }
+            }
+            if (preg_match('/__(header|result).*__/', $rowId)) {
+                unset($data[$rowId]);
+            } else {
+                $labels[$rowId] = isset($row['__header__']) ? $row['__header__'] : $rowId;
+                if ($data[$rowId]['__result__'] != 0) {
+                    $sum[$rowId] = $data[$rowId]['__result__'];
+                }
+                unset($data[$rowId]['__header__']);
+                unset($data[$rowId]['__result__']);
+            }
+        }
+
+        // grouping
+        asort($sum);
+        $topdata = array_slice($sum, 0, 5, true) + array_slice($sum, -5, 5, true);
+        foreach ($data as $rowId => $row) {
+            if (!isset($topdata[$rowId])) {
+                unset($data[$rowId]);
+            }
+        }
+
+        $this->data = $data;
+
+        /* Additional params. */
+        $this->setParam('graph', 'Line');
+        $this->setParam('labels', $labels);
+        $this->setParam('subtitle', $postingtypes[$display]);
+        $this->setParam('invert', true);
+
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/fima/lib/ReportGraph.php b/fima/lib/ReportGraph.php
new file mode 100644 (file)
index 0000000..1f2f3ba
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * Fima_ReportGraph:: defines an API for implementing report graphs for Fima.
+ *
+ * $Horde: fima/lib/ReportGraph.php,v 1.0 2008/06/23 23:43:00 trt Exp $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+/** PEAR Image Graph */
+require_once 'Image/Graph.php'; 
+
+/**
+ * Fima_ReportGraph class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_ReportGraph {
+
+    /**
+     * Hash containing report parameters.
+     *
+     * @var array
+     */
+    var $_params = array();
+    
+    /**
+     * Array containing the data after execution of the report.
+     *
+     * @var mixed
+     */
+    var $_data = array();
+    
+    /**
+     * Bytes containing the report graph after execution of the report.
+     *
+     * @var bytes
+     */
+    var $_graph = null;
+    var $_plotarea = null;
+    var $_legend = null;
+    
+    /**
+     * Constructor - just store the $params in our newly-created
+     * object. All other work is done by initialize().
+     *
+     * @param array $data    The dataset.
+     * @param array $params  Any parameters needed for this driver.
+     */
+    function Fima_ReportGraph($data = array(), $params = array(), $errormsg = null)
+    {
+        $this->_data = $data;
+        $this->_params = $params;
+        if (is_null($errormsg)) {
+            $this->_errormsg = _("The Finances report graphs are not currently available.");
+        } else {
+            $this->_errormsg = $errormsg;
+        }
+    }
+
+    /*
+     * Executes the report.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function execute()
+    {
+        /* Create graph. */
+        $this->_graph =& Image_Graph::factory('graph', array($this->_style['width'], $this->_style['height']));
+        $this->_graph->displayErrors();
+
+        /* Add Font. */
+        $font =& $this->_graph->addNew('font', $this->_style['font-family']);
+        $font->setColor($this->_style['font-color']);
+        $font->setSize($this->_style['font-size']);
+        $this->_graph->setFont($font);
+        
+        /* Plot and Legend. */
+        $title =& Image_Graph::factory('title', array(isset($this->_params['title']) ? $this->_params['title'] : _("Report"), $this->_style['header-size']));
+        $title->setAlignment(IMAGE_GRAPH_ALIGN_BOTTOM | IMAGE_GRAPH_ALIGN_CENTER_X);
+        $subtitle =& Image_Graph::factory('title', array(isset($this->_params['subtitle']) ? $this->_params['subtitle'] : '', $this->_style['subheader-size']));
+        $this->_plotarea =& Image_Graph::factory('plotarea');
+        $this->_legend =& Image_Graph::factory('legend');
+        $this->_graph->add(
+            Image_Graph::vertical(
+                Image_Graph::vertical(
+                    $title,
+                    $subtitle,
+                    60
+                ),
+                Image_Graph::vertical(
+                    $this->_plotarea,
+                    $this->_legend,
+                    88
+                ),
+                10
+            )
+        );
+        $this->_legend->setPlotarea($this->_plotarea);
+        $this->_legend->setAlignment(IMAGE_GRAPH_ALIGN_CENTER);
+
+        /* Execute. */
+        $execute = $this->_execute();
+        if (is_a($execute, 'PEAR_Error')) {
+            return $execute;
+        }
+
+        /* Log the execution of the report in the history log. */
+        $history = &Horde_History::singleton();
+        $history->log('fima:reportgraph', array('action' => 'execute'), true);
+        
+        return true;
+    }
+    
+    /**
+     * Returns the graph of this report (if any).
+     *
+     * @return bytes   Image data
+     */
+    function getGraph()
+    {
+        header('Expires: Mon, 01 Jan 1970 00:00:00 GMT');
+        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+        header('Pragma: public');
+        header('Cache-Control: no-store, no-cache, must-revalidate');
+        header('Cache-Control: pre-check=0, post-check=0, max-age=0');
+        header('Content-Type: image/jpeg');
+        header('Accept-Ranges: bytes');
+        #header('Content-Length: ' . );
+        header('Content-Disposition: inline');
+
+        $this->_graph->done(); 
+
+        return true;
+    }
+
+    /**
+     * Initialization of the report.
+     *
+     * @return boolean  True on success; PEAR_Error on failure.
+     */
+    function initialize()
+    {
+        /* Load styles. */
+        include_once($GLOBALS['registry']->get('themesfs') . '/report.inc');
+        $this->_style = $style;
+    }
+    
+    /**
+     * Attempts to return a concrete Fima_ReportGraph instance based on $driver.
+     *
+     * @param string    $driver     The type of the concrete Fima_ReportGraph subclass
+     *                              to return.  The class name is based on the
+     *                              storage driver ($driver).  The code is
+     *                              dynamically included.
+     *
+     * @param array     $data       The dataset.
+     * @param array     $params     (optional) A hash containing any additional
+     *                              configuration or connection parameters a
+     *                              subclass might need.
+     *
+     * @return mixed    The newly created concrete Fima_Driver instance, or
+     *                                 false on an error.
+     */
+    function &factory($driver = null, $data = null, $params = null)
+    {
+        if ($driver === null) {
+            $report =& new Fima_ReportGraph($data, $params, _("No report driver loaded"));
+            return $report;
+        }
+
+        require_once dirname(__FILE__) . '/ReportGraph/' . $driver . '.php';
+        $class = 'Fima_ReportGraph_' . $driver;
+        if (class_exists($class)) {
+            $report = &new $class($data, $params);
+            $result = $report->initialize();
+            if (is_a($result, 'PEAR_Error')) {
+                $report =& new Fima_ReportGraph($data, $params, sprintf(_("The Finances report graphs are not currently available: %s"), $result->getMessage()));
+            }
+        } else {
+            $report =& new Fima_ReportGraph($data, $params, sprintf(_("Unable to load the definition of %s."), $class));
+        }
+        
+        return $report;
+    }
+
+}
\ No newline at end of file
diff --git a/fima/lib/ReportGraph/Bar.php b/fima/lib/ReportGraph/Bar.php
new file mode 100644 (file)
index 0000000..d814ae6
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Fima_ReportGraph_Bar.
+ *
+ * $Horde: fima/lib/ReportGraph/Bar.php,v 1.0 2008/08/20 23:32:21 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/ReportGraph.php';
+
+/*
+ * Fima_ReportGraph_Bar class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_ReportGraph_Bar extends Fima_ReportGraph {
+
+    /*
+     * Constructs a new Bar ReportGraph.
+     */
+    function Fima_ReportGraph_Bar($data = array(), $params = array())
+    {
+        $this->_data = $data;
+        $this->_params = $params;
+
+        if (!isset($this->_params['invert'])) {
+            $this->_params['invert'] = false;
+        }
+    }
+
+    /*
+     * Executes the report graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Grid. */
+        $grid =& $this->_plotarea->addNew('line_grid');    
+        $gridfill =& Image_Graph::factory('Image_Graph_Fill_Array');
+        $gridfill->addColor($this->_style['grid']);
+        $grid->setFillStyle($gridfill);
+
+        /* Datasets. */
+        $datasets = array();
+        $datasetindex = array();
+        $ix = 0;
+        foreach ($this->_data as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $xd = $this->_params['invert'] ? $rowId : $colId;
+                $xx = $this->_params['invert'] ? $colId : $rowId;
+                if (!isset($datasetindex[$xd])) {
+                    $datasetindex[$xd] = $ix++;
+                    $datasets[$datasetindex[$xd]] =& Image_Graph::factory('dataset');
+                    $datasets[$datasetindex[$xd]]->setName($this->_params['labels'][$xd]);
+                }
+                $datasets[$datasetindex[$xd]]->addPoint($this->_params['labels'][$xx], $value);
+            }
+        }
+
+        $plot =& $this->_plotarea->addNew('bar', $params['stacked'] ? array($datasets, 'stacked') : array($datasets));
+        $plot->setLineColor($this->_style['line']);
+
+        /* Fill style. */
+        $fill =& Image_Graph::factory('Image_Graph_Fill_Array');
+        foreach ($datasetindex as $key => $value) {
+            if (isset($this->_style[$key])) {
+                $fill->addColor($this->_style[$key]);
+            } else {
+                $fill->addColor($this->_style['color' . $value]);
+            }
+        }
+        $plot->setFillStyle($fill); 
+
+        /* Axis. */
+        $axisx =& $this->_plotarea->getAxis(IMAGE_GRAPH_AXIS_X);
+        $axisy =& $this->_plotarea->getAxis(IMAGE_GRAPH_AXIS_Y);
+        $axisy->showLabel(IMAGE_GRAPH_LABEL_ZERO);
+        $axisy->setDataPreprocessor(Image_Graph::factory('Image_Graph_DataPreprocessor_Function', create_function('$value', 'return Fima::convertValueToAmount($value);'))); 
+
+        return true;
+    }
+    
+}
\ No newline at end of file
diff --git a/fima/lib/ReportGraph/Line.php b/fima/lib/ReportGraph/Line.php
new file mode 100644 (file)
index 0000000..330f645
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Fima_ReportGraph_Line.
+ *
+ * $Horde: fima/lib/ReportGraph/Line.php,v 1.0 2008/08/20 23:46:32 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/ReportGraph.php';
+
+/*
+ * Fima_ReportGraph_Line class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_ReportGraph_Line extends Fima_ReportGraph {
+
+    /*
+     * Constructs a new Line ReportGraph.
+     */
+    function Fima_ReportGraph_Line($data = array(), $params = array())
+    {
+        $this->_data = $data;
+        $this->_params = $params;
+        
+        if (!isset($this->_params['invert'])) {
+            $this->_params['invert'] = false;
+        }
+    }
+
+    /*
+     * Executes the report graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Grid. */
+        $grid =& $this->_plotarea->addNew('line_grid');    
+        $gridfill =& Image_Graph::factory('Image_Graph_Fill_Array');
+        $gridfill->addColor($this->_style['grid']);
+        $grid->setFillStyle($gridfill);
+
+        /* Datasets. */
+        $datasets = array();
+        $datasetindex = array();
+        $ix = 0;
+        foreach ($this->_data as $rowId => $row) {
+            foreach ($row as $colId => $value) {
+                $xd = $this->_params['invert'] ? $rowId : $colId;
+                $xx = $this->_params['invert'] ? $colId : $rowId;
+                if (!isset($datasetindex[$xd])) {
+                    $datasetindex[$xd] = $ix++;
+                    $datasets[$datasetindex[$xd]] =& Image_Graph::factory('dataset');
+                    $datasets[$datasetindex[$xd]]->setName($this->_params['labels'][$xd]);
+                }
+                $datasets[$datasetindex[$xd]]->addPoint($this->_params['labels'][$xx], $value);
+            }
+        }
+
+        /* Line style. */
+        foreach ($datasetindex as $key => $value) {
+          $plot =& $this->_plotarea->addNew('line', $datasets[$value]);
+          $plot->setLineColor($this->_style['line']);
+          
+          $line =& Image_Graph::factory('Image_Graph_Line_Solid', isset($this->_style[$key]) ? $this->_style[$key] : $this->_style['color' . $value]);
+          $line->setThickness(2);
+          $plot->setLineStyle($line);
+        }
+
+        /* Axis. */
+        $axisx =& $this->_plotarea->getAxis(IMAGE_GRAPH_AXIS_X);
+        $axisx->setFontAngle('vertical');
+        $axisx->setLabelOption('offset', -20);
+        $axisy =& $this->_plotarea->getAxis(IMAGE_GRAPH_AXIS_Y);
+        $axisy->showLabel(IMAGE_GRAPH_LABEL_ZERO);
+        $axisy->setDataPreprocessor(Image_Graph::factory('Image_Graph_DataPreprocessor_Function', create_function('$value', 'return Fima::convertValueToAmount($value);'))); 
+
+        return true;
+    }
+    
+}
\ No newline at end of file
diff --git a/fima/lib/ReportGraph/Pie.php b/fima/lib/ReportGraph/Pie.php
new file mode 100644 (file)
index 0000000..2f33618
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Fima_ReportGraph_Pie.
+ *
+ * $Horde: fima/lib/ReportGraph/Pie.php,v 1.0 2008/08/21 21:24:06 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Fima_Report */
+require_once FIMA_BASE . '/lib/ReportGraph.php';
+
+/*
+ * Fima_ReportGraph_Pie class.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Fima_ReportGraph_Pie extends Fima_ReportGraph {
+
+    /*
+     * Constructs a new Pie ReportGraph.
+     */
+    function Fima_ReportGraph_Pie($data = array(), $params = array())
+    {
+        $this->_data = $data;
+        $this->_params = $params;
+    }
+
+    /*
+     * Executes the report graph.
+     *
+     * @return mixed   True or PEAR Error
+     */
+    function _execute()
+    {
+        /* Datasets. */
+        $datasets = array();
+        $datasetindex = array();
+        $x = 0;
+        foreach ($this->_data as $ix => $dataset) {
+            $datasets[$ix] =& Image_Graph::factory('dataset');
+            foreach ($dataset as $key => $value) {
+                $datasetindex[$x++] = $key;
+                $datasets[$ix]->addPoint($this->_params['labels'][$key], $value);
+            }
+        }
+        $plot =& $this->_plotarea->addNew('pie', array($datasets));
+        $plot->setLineColor($this->_style['line']);
+
+        /* Fill style. */
+        $fill =& Image_Graph::factory('Image_Graph_Fill_Array');
+        foreach ($datasetindex as $key => $value) {
+            if (isset($this->_style[$value])) {
+                $fill->addColor($this->_style[$value]);
+            } else {
+                $fill->addColor($this->_style['color' . $key]);
+            }
+        }
+        $plot->setFillStyle($fill); 
+
+        /* Axis. */
+        $this->_plotarea->hideAxis();  
+        
+        /* Explode. */
+        if (isset($this->_params['explode'])) {
+            if (is_int($this->_params['explode']) || is_array($this->_params['explode'])) {
+                $plot->explode($this->_params['explode']);
+            } else {
+                $plot->explode(10, $this->_params['explode']);
+            } 
+        }
+        
+        /* Marker. */
+        if ($this->_params['marker']) {
+            $marker =& $plot->addNew('Image_Graph_Marker_Value', IMAGE_GRAPH_PCT_Y_TOTAL);
+            $marker->setDataPreprocessor(Image_Graph::factory('Image_Graph_DataPreprocessor_Formatted', '%0.1f%%'));
+            $marker->setFontSize($this->_style['font-size']);
+            $pointingmarker =& $plot->addNew('Image_Graph_Marker_Pointing_Angular', array(30, &$marker));
+            $plot->setMarker($pointingmarker);
+        }
+
+        return true;
+    }
+    
+}
\ No newline at end of file
diff --git a/fima/lib/UI/VarRenderer/fima.php b/fima/lib/UI/VarRenderer/fima.php
new file mode 100644 (file)
index 0000000..a9a164f
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * This file contains all Horde_UI_VarRenderer extensions required for editing
+ * accounts.
+ *
+ * $Horde: fima/lib/UI/VarRenderer/fima.php,v 1.0 2008/06/19 20:58:27 trt Exp $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Fima
+ */
+
+/** Horde_UI_VarRenderer */
+require_once 'Horde/UI/VarRenderer.php';
+
+/** Horde_UI_VarRenderer_html */
+require_once 'Horde/UI/VarRenderer/html.php';
+
+/**
+ * The Horde_UI_VarRenderer_fima class provides additional methods for
+ * rendering Fima specific Horde_Form_Type fields.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+class Horde_UI_VarRenderer_fima extends Horde_UI_VarRenderer_html {
+
+    function _renderVarInput_fima_dspostings($form, &$var, &$vars)
+    {
+        $varname = @htmlspecialchars($var->getVarName(), ENT_QUOTES, $this->_charset);
+        $value = $var->getValue($vars);
+
+        return sprintf('<input id="%sdelete" type="radio" class="radio" name="%s[type]" value="delete"%s /><label for="%sdelete">&nbsp;%s</label><br />',
+                       $varname,
+                       $varname,
+                       $value['type'] == 'delete' ? ' checked="checked"' : '',
+                       $varname,
+                       _("Delete postings."))
+            . sprintf('<input id="%sshift" type="radio" class="radio" name="%s[type]" value="shift"%s /><label for="%sshift">&nbsp;%s</label><br />',
+                      $varname,
+                      $varname,
+                      $value['type'] == 'shift' ? ' checked="checked"' : '',
+                      $varname,
+                      _("Shift postings to"))
+            . Fima::buildAccountWidget($varname . '[account]', $value['account'], 'onchange="document.getElementsByName(\'dspostings[type]\')[1].checked = true;"', false, false, array(array('type', $vars->get('type'))));
+    }
+
+    function _renderVarInput_fima_dssubaccounts($form, &$var, &$vars)
+    {
+        $varname = @htmlspecialchars($var->getVarName(), ENT_QUOTES, $this->_charset);
+        $value = $var->getValue($vars);
+
+        return sprintf('<input id="%snone" type="radio" class="radio" name="%s[type]" value="none"%s /><label for="%snone">&nbsp;%s</label><br />',
+                       $varname,
+                       $varname,
+                       $value['type'] == 'none' ? ' checked="checked"' : '',
+                       $varname,
+                       _("Keep subaccounts and postings."))
+            . sprintf('<input id="%sdelete" type="radio" class="radio" name="%s[type]" value="delete"%s /><label for="%sdelete">&nbsp;%s</label><br />',
+                      $varname,
+                      $varname,
+                      $value['type'] == 'delete' ? ' checked="checked"' : '',
+                      $varname,
+                      _("Delete subaccounts and postings."))
+            . sprintf('<input id="%sshift" type="radio" class="radio" name="%s[type]" value="shift"%s /><label for="%sshift">&nbsp;%s</label><br />',
+                      $varname,
+                      $varname,
+                      $value['type'] == 'shift' ? ' checked="checked"' : '',
+                      $varname,
+                      _("Delete subaccounts and shift postings to"))
+            . Fima::buildAccountWidget($varname . '[account]', $value['account'], 'onchange="document.getElementsByName(\'dssubaccounts[type]\')[2].checked = true;"', false, false, array(array('type', $vars->get('type'))));
+    }
+
+}
diff --git a/fima/lib/base.php b/fima/lib/base.php
new file mode 100644 (file)
index 0000000..0e30244
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Fima base application file.
+ *
+ * $Horde: fima/lib/base.php,v 1.0 2008/04/25 00:15:28 trt Exp $
+ *
+ * This file brings in all of the dependencies that every Fima script will
+ * need, and sets up objects that all scripts use.
+ */
+
+// Check for a prior definition of HORDE_BASE (perhaps by an auto_prepend_file
+// definition for site customization).
+if (!defined('HORDE_BASE')) {
+    @define('HORDE_BASE', dirname(__FILE__) . '/../..');
+}
+
+// Load the Horde Framework core, and set up inclusion paths.
+require_once HORDE_BASE . '/lib/core.php';
+
+// Registry.
+$session_control = Util::nonInputVar('session_control');
+if ($session_control == 'none') {
+    $registry = &Registry::singleton(HORDE_SESSION_NONE);
+} elseif ($session_control == 'readonly') {
+    $registry = &Registry::singleton(HORDE_SESSION_READONLY);
+} else {
+    $registry = &Registry::singleton();
+}
+
+if (is_a(($pushed = $registry->pushApp('fima', !defined('AUTH_HANDLER'))), 'PEAR_Error')) {
+    if ($pushed->getCode() == 'permission_denied') {
+        Horde::authenticationFailureRedirect();
+    }
+    Horde::fatal($pushed, __FILE__, __LINE__, false);
+}
+$conf = &$GLOBALS['conf'];
+@define('FIMA_TEMPLATES', $registry->get('templates'));
+
+// Find the base file path of Fima.
+if (!defined('FIMA_BASE')) {
+    @define('FIMA_BASE', dirname(__FILE__) . '/..');
+}
+
+// Notification system.
+$notification = &Notification::singleton();
+$notification->attach('status');
+
+// Fima base library
+require_once FIMA_BASE . '/lib/Fima.php';
+require_once FIMA_BASE . '/lib/Driver.php';
+
+// Horde libraries.
+require_once 'Horde/Help.php';
+require_once 'Horde/History.php';
+
+// Start output compression.
+Horde::compressOutput();
+
+// Set the timezone variable.
+NLS::setTimeZone();
+
+// Create a share instance.
+require_once 'Horde/Share.php';
+$GLOBALS['fima_shares'] = &Horde_Share::singleton($registry->getApp());
+
+Fima::initialize();
diff --git a/fima/lib/prefs.php b/fima/lib/prefs.php
new file mode 100644 (file)
index 0000000..024e78e
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * $Horde: fima/lib/prefs.php,v 1.0 2008/09/23 20:12:08 trt Exp $
+ *
+ * Copyright 2002-2006 Chuck Hagenbuch <chuck@horde.org>
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Thomas Trethan <thomas@trethan.net>
+ * @package Fima
+ */
+
+function handle_ledgerselect($updated)
+{
+    global $prefs;
+
+    $active_ledger = Util::getFormData('active_ledger');
+    if (!is_null($active_ledger)) {
+        $ledgers = Fima::listLedgers();
+        if (is_array($ledgers) && array_key_exists($active_ledger, $ledgers)) {
+            $prefs->setValue('active_ledger', $active_ledger);
+            $updated = true;
+        }
+    }
+    return $updated;
+}
+
+function handle_closedperiodselect($updated)
+{
+    global $prefs;
+    
+    $period = Util::getFormData('closedperiod');
+    if ((int)$period['year'] > 0 && (int)$period['month'] > 0) {
+        $period = mktime(0, 0, 0, $period['month'] + 1, 0, $period['year']);
+    } else {
+        $period = 0;
+    }
+    $prefs->setValue('closed_period', $period);
+    $updated = true;
+    
+    return $updated;
+}
\ No newline at end of file
diff --git a/fima/lib/version.php b/fima/lib/version.php
new file mode 100644 (file)
index 0000000..3f4500b
--- /dev/null
@@ -0,0 +1 @@
+<?php define('FIMA_VERSION', '1.0.1') ?>
\ No newline at end of file
diff --git a/fima/locale/de_DE/LC_MESSAGES/fima.mo b/fima/locale/de_DE/LC_MESSAGES/fima.mo
new file mode 100644 (file)
index 0000000..a411baf
Binary files /dev/null and b/fima/locale/de_DE/LC_MESSAGES/fima.mo differ
diff --git a/fima/locale/de_DE/help.xml b/fima/locale/de_DE/help.xml
new file mode 100644 (file)
index 0000000..25fb1c6
--- /dev/null
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- $Horde: fima/locale/de_DE/help.xml,v 1.0 2009/02/18 07:57:38 trt Exp $ -->
+<help>
+
+<entry id="fima-overview">
+    <title>Fima: Ãœbersicht</title>
+    <heading>Was ist Fima?</heading>
+    <para>
+    Fima steht für Finanz Manager und dient der doppelten Buchführung. Es ist ein flexibles Werkzeug um Ihre Finanzen zu organisieren sowie Ausgaben, Einkünfte und Vermögensstände zu verfolgen. Es ermöglicht die Erstellung von Budgets und Vorschauwerten und bietet zahlreiche verschiedene Auswertungen zur Analyse.
+    </para>
+    <heading>Hauptfunktionalitäten:</heading>
+    <para>* freie und flexible Struktur des Kontenplans</para>
+    <para>* Vermögenswerte organisiersen und nachverfolgen</para>
+    <para>* unabhängige Datenkreise: Ist-Abrechnung, Budget, Vorschau</para>
+    <para>* flexible and detaillierte Auswertungen mit verschiedenen Parametern</para>
+    <para>* grafische Auswertungsdiagramme</para>
+</entry>
+
+<entry id="fima-options">
+       <title>Fima: Einstellungen</title>
+       <heading>Aktive Konfiguration</heading>
+       <para>* Ihr aktives Buch: wählen Sie Ihr aktives Buch</para>
+       <para>* Ihr aktiver Datenkreis: wählen Sie Ihren aktiven <ref module="fima" entry="postings-postingtypes">Datenkreis</ref></para>
+       <para>* Geschlossen mit Zeitraum: wählen Sie das Ende des Zeitraums, innerhalb dessen das Anlegen und Bearbeiten von Buchungen gesperrt ist; wählen Sie &quot;Keine&quot; um alle Buchungen offen zu lassen</para>
+       <heading>Oberflächen-Einstellungen</heading>
+       <para>* Anzahl der Buchungen pro Seite: geben Sie die Anzahl der Buchungen ein, die pro Seite angezeigt werden</para>
+       <para>* Bei der Anzeige von Buchungen, bei welcher Seite möchten Sie beginnen: erste oder letzte Seite</para>
+       <para>* Buchungen sortieren nach: wählen Sie die Spalte aus, nach welcher Buchungen sortiert werden</para>
+       <para>* Dann: wählen Sie eine alternative Spalte aus, nach welcher Buchungen sortiert werden</para>
+       <para>* Sortierrichtung: aufsteigen oder absteigend</para>
+       <para>* Wählen Sie das Format für Platzhalter in der Textsuche: DOS Platzhalter (* und ?), SQL Platzhalter (% und _) oder keine</para>
+       <para>* Wählen Sie das FOrmat für Beträge: wählen Sie Ihre bevorzugte Notation</para>
+       <para>* Ausgaben mit negativem Vorzeichen eingeben: wählen Sie, ob Sie bei beim Eingeben von Buchungen das negative Vorzeichen für Ausgaben weglassen möchten</para>
+       <para>* Möchten Sie das Löschen von Buchungen bestätigen: wählen Sie, ob Sie das Löschen von Buchungen bestätigen möchten</para>
+       <para>* Wählen Sie die Grafikgröße für Auswertungsdiagramme: die Abmessungen der erstellten Bilder in Pixel</para>
+</entry>
+
+<entry id="accounts-overview">
+       <title>Konten: Ãœbersicht</title>
+       <para>
+       Der Kontenplan ist in drei Ebenen gegliedert:
+       </para>
+       <heading>Kontokategorie</heading>
+       <para>
+       Kontokategorien dienen der allgemeinen Aufsummierungen von verschiedenen Konten. Jedes Konto ist einer einzigen Kategorie zugeordnet. Es existieren drei verschiedene Kontokategorien:
+       </para>
+       <para>* Einnahmen (zB. Gehalt, Gewinne, Spenden, etc.)</para>
+       <para>* Ausgaben (zB. Verpflegung, Miete, Kleidung, etc.)</para>
+       <para>* Vermögen (zB. Bargeld, Kreditkarten, Sparbücher, etc.)</para>
+       <para>
+       Es ist notwendig pro Buchungstransaktion ein Vermögenskonto anzugeben. Dadurch ist es möglich Ihre Vermögensbestände sowie jegliche Geldtransfers (Buchungen von einem Vermögenskonto auf ein anderes) nachzuverfolgen.
+       </para>
+       <tip>
+       Tipp: Wenn Sie nur an Einnahmen und Ausgaben interessiert sind und keine Vermögenswerte verwalten wollen, legen Sie nur ein einziges Vermögenskonto an, welches automatisch für alle Buchungen ausgewählt wird.
+       </tip>
+       <heading>Kontogruppen, Hauptkonten, Unterkonten</heading>
+       <para>
+       Ein Konto dient der Summierung von Ã¤hnlichen Einnahmens-, Ausgaben- oder Vermögenspositionen. Ein Konto wird durch eine Nummer, einen Namen und eine Kontokategroie definiert. Eine Kontonummer ist unterteilt in zwei Teile: XXYY, wobei XX das Hauptkonto und YY das Unterkonto bezeichnet (zB. in der Kontogruppe 12: 1200 ist das Hauptkonto, 1230 und 1245 sind beides Unterkonten). Es können 100 Kontogruppen zu je 100 Konten angelegt werden, wobei die Nutzung von Unterkonten nicht erforderlich ist.
+       </para>
+       <para>
+       Zusätzlich ist es möglich eine Langbeschreibung pro Konto anzugeben und ob die Buchungen auf diesem Konto standardmäßig als a.o. markiert werden sollen (siehe <ref module="fima" entry="postings-overview">Buchungen Ãœbersicht</ref>). Weiters kann ein Konto abgeschlossen werden um neue Buchungen zu unterbinden, jedoch existierende zu behalten.
+       </para>
+       <tip>
+       Tipp: Bei der Nutzung von Unterkonten vermeiden Sie es direkt auf das Hauptkonto XX00 zu buchen, da Sie sonst diese Buchungen beim Summieren auf Kontogruppen in Auswertungen nicht mehr identifizieren können. Es empfielt sich für allgemeine Buchungen, die zu keinem Unterkonto passen, ein separates Unterkonto zu erstellen (zB. XX99).
+       </tip>
+</entry>       
+
+<entry id="accounts-delete">
+       <title>Konten: Löschen eines Kontos</title>
+       <para>
+       Beim Löschen eines Kontos müssen Sie angeben, wie Sie Buchungen handhaben wollen:
+       </para>
+       <para>* Buchungen löschen: alle existierenden Buchungen, die auf das betroffene Konto gebucht wurden, löschen</para>
+       <para>* Buchungen umbuchen: alle existierenden Buchungen auf ein anderes Konto derselben Kategorie verschieben</para>
+       <para>
+       Sie müssen auch angeben, wie Sie etwaige Unterkonten und deren Buchungen handhaben wollen:
+       </para>
+       <para>* Unterkonten und Buchungen behalten: nichts tun</para>
+       <para>* Unterkonten und Buchungen löschen: alle existierenden Unterkonten und Buchungen ebenfalls löschen</para>
+       <para>* Unterkonten löschen und Buchungen umbuchen: alle existierenden Unterkonten löschen und zuvor jegliche existierenden Buchungen auf ein anderes Konto derselben Kategorie verschieben</para>
+</entry>
+
+<entry id="postings-overview">
+       <title>Buchungen: Ãœbersicht</title>
+       <para>
+       Ein Buchungssatz hat folgende Attribute:
+       </para>
+       <para>* Datenkreis: siehe <ref module="fima" entry="postings-postingtypes">Datenkreise</ref></para>
+       <para>* Datum: Datum der Buchung (Tag, Monat, Jahr)</para>
+       <para>* Vermögenskonto: für jede Buchung ist die Angabe eines Vermögenskontos zur Verfolgung Ihre Vermögenswerte erforderlich</para>
+       <para>* Bewegungskonto: das Konto der Transaktion, Ã¼blicherweise vom Typ Einnahmen oder Ausgaben; bei Vermögensumlagen (zB. Bargeldabhebung von einem Bankkonto) wählen Sie das entsprechende Vermögenskonto als Bewegungskonto</para>
+       <para>* Anmerkung: pro Buchung kann eine kurze Anmerkung eingegeben werden (max. 255 Zeichen)</para>
+       <para>* Betrag: positive Beträge erhöhen und negative Beträge vermindern das gewählte Vermögenskonto</para>
+       <para>* a.o.: Buchungen können als außerordentlich markiert werden (besonders groß, nicht geplant, ...)</para>
+</entry>
+
+<entry id="postings-postingtypes">
+       <title>Buchungen: Datenkreise</title>
+       <para>
+       Jede Buchung ist einem einziges Datenkreis zugeordnet. Es existieren drei unterschiedliche Datenkreise:
+       </para>
+       <heading>Ist-Abrechnung</heading>
+       <para>
+       Ist-Buchungen bestehen aus derzeitigen Einnahmen und Ausgaben, die mittels verschiedener Auswertungen analysiert werden können. Es spiegelt Ihre aktuelle finanzielle Situation wider.
+       </para>
+       <tip>
+       Tipp: Prüfen Sie stets auf Vermögensdifferenzen, bevor Sie mit der Eingabe neuer Buchungen beginnen. Buchen Sie jegliche Differenzen auf ein spezielles Konto, um wenigstens korrekte Vermögenskonten zu haben. Prüfen Sie auch Ihre Kontoauszüge auf fehlende Buchungen.
+       </tip>
+       <tip>
+       Tipp: Versuchen Sie Ihre Buchungen regelmäßig zu erfassen. Fragen Sie immer nach einem Kassenbon oder schreiben Sie Ihre Ausgaben auf einem Zettel auf, damit Sie keine Ausgaben vergessen, wenn Sie diese nicht sofort erfassen.
+       </tip>
+       <heading>Budget</heading>
+       <para>
+       Die Verwendung eines Budgets ist optional und bietet Kontrolle Ã¼ber Ihre zukünftige finanzielle Entwicklung ebenso wie die Analysemöglichkeit Ihrer früheren Finanzziele. Dadurch ist es möglich Ihren finanziellen Spielraum früh zu erkennen.
+       </para>
+       <tip>
+       Tipp: Versuchen Sie Ihre wesentliche Budget-Zielsetzungen zu erreichen. Klammern Sie sich jedoch nicht krampfhaft daran, denn es wird immer Ereignisse geben, die Sie nicht in Ihrem Budget berücksichtigt haben.
+       </tip>
+       <tip>
+       Tipp: Wenn Sie einen detaillierten Kontenplan verwenden, erstellen Sie Ihr Budget auf Basis von Hauptkonten anstelle von Unterkonten, da dies sonst sehr zeitintensiv wäre.
+       </tip>
+       <tip>
+       Tipp: Erfassen Sie jegliche Abweichungen zwischen Ihrer Ist-Abrechnung und Ihrem Budget. Dadurch sind auch Ihre budgetierten Vermögenswerte aktuell.
+       </tip>
+       <tip>
+       Tipp: Es besteht die Möglichkeit Ihr Budget durch die Funktion <ref module="fima" entry="postings-transfer">Buchungen transferieren</ref> als Kopie der Buchungen des Vorjahrs mit optionaler Summierung zu erstellen und nachträglich anzupassen.
+       </tip>
+       <heading>Vorschau</heading>
+       <para>
+       Nach der Erstellung des Budget wird dieses in der Regel nicht mehr verändert. Alle weiteren Anpassung können dann in der Vorschaurechnung berücksichtigt werden, welche somit ein aktualisiertes Budget darstellt.
+       </para>
+       <tip>
+       Tipp: Prüfen Sie auf Abweichungen zwischen den Summen von Ist-Abrechnung und Budget/Vorschau und passen Sie die weitere Vorschaurechnung an.
+       </tip>
+       <tip>
+       Tipp: Erfassen Sie jegliche Abweichungen zwischen Ihrer Ist-Abrechnung und Vorschau. Dadurch sind auch Ihre Vorschau-Vermögenswerte aktuell.
+       </tip>
+       <tip>
+       Tipp: Es besteht die Möglichkeit Ihre Vorschaurechnung durch die Funktion <ref module="fima" entry="postings-transfer">Buchungen transferieren</ref> als Kopie Ihres Budgets zu erstellen und nachträglich anzupassen.
+       </tip>
+</entry>
+
+<entry id="postings-addedit">
+       <title>Buchungen: Buchungen erstellen/bearbeiten</title>
+       <para>
+       Für die vereinfachte Eingabe wird bei neuen Buchungen das Datum und Vermögenskonto vom darüberliegenden Eintrag kopiert, kann aber nachträglich geändert werden.
+       </para>
+       <para>
+       Ausgaben haben Ã¼blicherweise negative Beträge, können jedoch aus Komfort ohne negatives Vorzeichen eingegeben werden (siehe <ref module="fima" entry="fima-options">Oberflächen-Einstellungen: Ausgaben mit negativem Vorzeichen eingeben?</ref>). Bei Vermögensumlagen (Transfer von einem Vermögenskonto auf ein anderes) betrifft der eingegebene Betrag stets das gewählte Verm*genskonto (zB. -10 bedeutet eine Verminderung des gewählten Vermögenskontos um 10 und folglich eine Erhöhung um 10 des als Bewegungskonto ausgewählten Vermögenskontos).
+       </para>
+       <tip>
+       Tipp: Bei der Eingabe neuer Buchungen können Konten auch ausgewählt werden, indem man einfach die Kontonummer auf der Tastatur eingibt anstatt die Listenfelder zu durchsuchen. Sie werden rasch die am häufigsten verwendeten Kontonummer kennen und sich so eine Menege Zeit bei der Eingabe neuer Buchungen sparen.
+       </tip>
+       <tip>
+       Tipp: Bei der Eingabe neuer Buchungen können Sie mathematische Operationen im Betragsfeld ausführen. Geben Sie einfach den mathematischen Ausdruck (zB. 3+2*5) ein und das Ergebnis wird automatisch berechnet.
+       </tip>
+       <tip>
+       Tipp: Bei der Eingabe neuer Buchungen für <ref module="fima" entry="postings-postingtypes">Budget oder Vorschau</ref> können Sie automatisch einen Satz gleicher Buchungen für jeden Monat eines gezielten Jahres erstellen. In der ersten Zeile geben Sie ein beliebiges Datum des gewünschten Jahres ein, wählen ein Vermögens- und Bewegungskonto und geben eine Anmerkung (optional) und Betrag ein. Drücken Sie anschließend den Knopf &quot;Auto-Füllen&quot;, um die eingegeben Werte für alle Monate zu kopieren.
+       </tip>
+</entry>
+
+<entry id="postings-shift">
+       <title>Buchungen: Buchungen umbuchen</title>
+       <para>
+       Buchen Sie mehrere Buchungen um, um diese gemeinsam in einen anderen Datenkreis zu verschieben oder deren Vermögens- oder Bewegungskonto zu Ã¤ndern.
+       </para>
+</entry>
+
+<entry id="postings-transfer">
+       <title>Buchungen: Buchungen transferieren</title>
+       <para>
+       Transferieren Sie alle Buchungen eines bestimmten Datenkreise innerhalb eines gezielten Zeitraums in einen anderen Datenkreis mit folgenden Optionen:
+       </para>
+       <para>* Transfer von: Quelldatenkreis, von dem die Buchungen transferiert werden sollen</para>
+       <para>* Zeitraum von: Quellzeitraum, innerhalb dessen die Buchungen transferiert werden sollen</para>
+       <para>* Ursprüngliche Buchungen behalten: auswählen um die gewählten Buchungen zu kopieren, andernfalls um diese zu verschieben</para>
+       <para>* Nicht summieren: Buchungen unverändert lassen</para>
+       <para>* Summieren durch Kombination: Buchungen summieren durch Gruppierung pro Kombination Vermögens-/Bewegungskonto</para>
+       <para>* Summieren durch Buchen gegen: Buchungen summieren durch getrennte Gruppierung pro Vermögens- und Bewegungskonto, wobei jeweils gegen ein spezielle (Dummy-)Konto gebucht wird</para>
+       <para>* Transfer nach: Zieldatenkreis, in den die Buchungen transferiert werden sollen</para>
+       <para>* Zeitraum bis: Zielzeitraum, inner den die Buchungen transferiert werden sollen</para>
+       <para>* Existierende Buchungen löschen: zuvor alle existierenden Buchungen im Zieldatenkreis innerhalb des Zielzeitraums löschen</para>
+       <tip>
+       Tipp: Sie könnnen das <ref module="fima" entry="postings-postingtypes">Budget (oder die Vorschau)</ref> durch Transfer existiernder Buchungen von der Ist-Abrechnung (oder dem Budget) erstellen. Achten Sie darauf, die ursprünglichen Buchungen zu behalten. Es empfielt sich, die Buchungen für das Budget (oder die Vorschau) zu summieren um lediglich Buchungen auf der Ebene von Hauptkonto zu erhalten. Dies erleichtert später Anpassungen.
+       </tip>
+       <tip>
+       Tipp: Es besteht die Möglichkeit alle Buchungens eines Jahrs auf einmal zu transferieren. Wählen Sie &quot;Alle&quot; als Quell- und Zielmonat, um den Buchungsmonat nicht Ã¤ndern.
+       </tip>
+</entry>
+
+<entry id="postings-search">
+       <title>Buchungen: Buchungen suchen</title>
+       <para>
+       Bei der Suche von Buchungen können Sie folgende Filter verwenden:
+       </para>
+       <para>* Datenkreis: zu filternder Datenkreis</para>
+       <para>* Anfangsdatum: Beginn des zu filternden Zeitraums</para>
+       <para>* Enddatum: Ende des zu filternden Zeitraums</para>
+       <para>* Vermögenskonten: es werden Buchungen mit dem/den ausgewählten Konten als Vermögens- oder Bewegungskonto (bei Vermögensumlagen) gefiltert</para>
+       <para>* Bewegungskonten: es werden Buchungen mit dem/den ausgewählten Konten als Bewegungskonto gefiltert</para>
+       <para>* Anmerkung: Sie können Platzhalter beim Durchsuchen der Anmerkung verwenden (siehe <ref module="fima" entry="fima-options">Oberflächen-Einstellungen: Wählen Sie das Format für Platzhalter in der Textsuche</ref>).</para>
+       <para>* Anfangsbetrag: minimaler Betrag</para>
+       <para>* Endbetrag: maximaler Betrag</para>
+       <para>* a.o. Buchungen: alle oder keine a.o. Buchungen filtern; leer lassen um alle Buchungen zu erhalten</para>
+       <tip>
+       Tipp: Es können mehrere Vermögens- und Bewegungskonten durch Halten der Umschalt- oder Steuerungstaste ausgewählt werden.
+       </tip>
+       <tip>
+       Tipp: Beim Filtern nach Beträgen vergessen Sie nicht Ausgaben mit negativem Vorzeichen einzugeben.
+       </tip>
+       <tip>
+       Tipp: Eine Suchabfrage wird für die derzeitige Sitzung solange gespeichert, bis diese beendet oder eine neue Suche gestartet wird. Ein Anzeichen für eine aktive Sucheabfrage ist das Mistkübel-Symbol in der Ãœberschriftszeile.
+       </tip>
+</entry>
+
+<entry id="reports-overview">
+       <title>Auswertungen: Ãœbersicht</title>
+       <para>
+       Bei der Erstellung von Auswertungen können Sie folgende Parameter verwenden:
+       </para>
+       <para>* Auswertung: verschiedene <ref module="fima" entry="reports-details">Auswertungen</ref></para>
+       <para>* Anzeige: Datenkreis auswählen (manche Auswertungen benutzen nur den ersten Wert, andere alle Werte)</para>
+       <para>* Zeitraum: Beginn und Ende des Auswertungszeitraums</para>
+       <para>* Referenz: Beginn und Ende des Referenz-Auswertungszeitraums (bei der Verwendung von Referenz als Datenkreis für die Anzeige)</para>
+       <para>* Konten: Buchungen mit den ausgewählten Konten als Bewegungskonto werden ausgewertet</para>
+       <para>* Kumulieren: Teilergebnisse im gewählten Zeitraum werden summieren</para>
+       <para>* Unterkonten: zusätzlich Unterkonten anzeigen anstatt nur Hauptkonten</para>
+       <para>* Leerzeilen: Zeilen ohne Ergebnisse anzeigen</para>
+       <para>* Jährlich: Ergebnisse nach Jahren anstatt Monaten gruppieren</para>
+       <para>* Diagramm: eine Auswertungsgrafik anstelle von einer tabellarischen Ansicht anzeigen</para>
+       <para>
+       Alle Auswertungen verwenden nur einen Teil der Parameter. Siehe <ref module="fima" entry="reports-details">Auswertungsdetails</ref> für eine Beschreibung jeder Auswertung und der verwendeten Parameter.
+       </para>
+</entry>
+
+<entry id="reports-details">
+       <title>Auswertungen: Details</title>
+
+       <heading>Gesamtübersicht</heading>
+       <para>* Beschreibung: Kontokategorien, Ergebnisse und Vermögen pro Datenkreis</para>
+       <para>* Parameter: Anzeige (alle), Zeitraum, (Referenz), Konten, Diagramm</para>
+       <para>* Diagramm: Balkendiagramm</para>
+       
+       <heading>Periodenübersicht</heading>
+       <para>* Beschreibung: Kontokategorien, Ergebnisse und Vermögen pro Zeitraum</para>
+       <para>* Paramater: Anzeige (erstes), Zeitraum, (Referenz), Konten, Kumulieren, Leerzeilen, Jährlich, Diagramm</para>
+       <para>* Diagramm: Liniendiagramm</para>
+       
+       <heading>Kontoübersicht</heading>
+       <para>* Beschreibung: Datenkreise pro Zeitraum</para>
+       <para>* Parameter: Anzeige (alle), Zeitraum, (Referenz), Konten, Kumulieren, Leerzeilen, Jährlich, Diagramm</para>
+       <para>* Diagramm: Liniendiagramm</para>
+       
+       <heading>Vermögensübersicht</heading>
+       <para>* Beschreibung: Vermögenskonten pro Zeitraum</para>
+       <para>* Parameter: Anzeige (erstes), Zeitraum, (Referenz), Kumulieren, Unterkonten, Leerzeilen, Jährlich, Diagramm</para>
+       <para>* Diagramm: Liniendiagramm, Besten 5</para>
+
+       <heading>Analyse</heading>
+       <para>* Beschreibung: Datenkreise pro Konten</para>
+       <para>* Parameter: Anzeige (alle), Zeitraum, (Referenz), Konten, Unterkonten, Leerzeilen, Diagramm</para>
+       <para>* Diagramm: Tortendiagramm, Besten 5 (Rest gruppiert) pro Kontokategorie</para>
+
+       <heading>Trend</heading>
+       <para>* Beschreibung: Zeitraum pro Konto</para>
+       <para>* Parameter: Anzeige (erstes), Zeitraum, (Referenz), Konten, Unterkonten, Leerzeilen, Jährlich, Diagramm</para>
+       <para>* Diagramm: Liniendiagramm, Besten 5 pro Kontokategorie</para>
+</entry>
+
+<entry id="exemplar-overview">
+       <title>Musterbeispiel: Ãœbersicht</title>
+       <para>
+       Dieses Musterbeispiel demonstriert die Verwendung der Hauptfunktionalitäten von Fima. Folgen Sie den Anweisungen Schritt für Schritt in der angegebenen Reihenfolge.
+       </para>
+</entry>
+
+<entry id="exemplar-accounts">
+       <title>Musterbeispiel: 1) Konten</title>
+       <heading>Vermögen</heading>
+       <para>* 0100 Bargeld</para>
+       <para>* * 0110 Person A</para>
+       <para>* * 0120 Person B</para>
+       <para>* 0200 Girkontoo</para>
+       <para>* 0300 Sparbuch</para>
+       <para>* 0400 Lebensversicherung</para>
+       <heading>Einnahmen</heading>
+       <para>* 0000 Saldovortrag</para>
+       <para>* 1000 Gehalt</para>
+       <para>* 1900 Andere Einnahmen</para>
+       <para>* * 1910 Beihilfen</para>
+       <para>* * 1999 Diverses</para>
+       <heading>Ausgaben</heading>
+       <para>* 2000 Verpflegung</para>
+       <para>* 3000 Heim</para>
+       <para>* * 3010 Miete</para>
+       <para>* * 3020 Strom</para>
+       <para>* * 3030 Einrichtung</para>
+       <para>* 3500 Auto</para>
+       <para>* * 3510 Versicherung</para>
+       <para>* * 3520 Benzin</para>
+       <para>* 4000 Person A</para>
+       <para>* * 4010 Kleidung</para>
+       <para>* * 4020 Freizeit</para>
+       <para>* 5000 Person B</para>
+       <para>* * 5010 Kleidung</para>
+       <para>* * 5020 Freizeit</para>
+       <para>* 9900 Andere Ausgaben</para>
+       <tip>
+       Tipp: Der obige Kontenplan ist nur ein kleines Beispiel. Tatsächlich gibt es eine Menge anderer Sachen zu berücksichtigen (zB. Verkäufe, Spenden, Lotterie, Zinsen, TV/Internet, Elektronik, Renovierung, Reparaturen, Haushalt, Sanitärartikel, Medikamente, Haustiere, Frisör, Parkgebühren, Maut, Autoreparaturen, Ersatzteile, Bildung, Telefonie, Partys, Musik/Video, Spiele, Sport, Bücher, Rauchen, Urlaub, Geschenke, Steuern, etc.)
+       </tip>
+</entry>
+
+<entry id="exemplar-opening">
+       <title>Musterbeispiel: 2) Anfangsbestände</title>
+       <para>
+       Vor der Eingabe regelmäßiger Buchungen erfassen wir unsere Vermögenswerte und buchen sie gegen das Konto 0000 Saldovortrag (zB. mit dem Datum 01.01.2008):
+       </para>
+       <para>Vermögenskonto | Bewegungskonto | Betrag</para>
+       <para>* 0110 Bargeld - Person A | 0000 Saldovortrag | 133,50</para>
+       <para>* 0120 Bargeld - Person B | 0000 Saldovortrag | 420,30</para>
+       <para>* 0200 Girokonto | 0000 Saldovortrag | -3.412,50</para>
+       <para>* 0300 Sparbuch | 0000 Saldovortrag | 6.850,20</para>
+       <para>* 0400 Lebensversicherung | 0000 Saldovortrag | 10.500,00</para>  
+       <para>In Summe beträgt unser Vermögen daher 14.491,50.</para>
+       <tip>
+       Tipp: Benutzen Sie ein eigenes Konto der Kategorie Einkommen für die Anfangsbestände. Dadurch weisen Auswertungen korrekte Beträge aus und Vermögensstände können verfolgt werden.
+       </tip>
+</entry>
+
+<entry id="exemplar-postings">
+       <title>Musterbeispiel: 3) Buchungen</title>
+       <para>
+       Nach der Erfassung unserer <ref module="fima" entry="exemplar-opening">Anfangsbestände</ref> beginnen wir einige Buchungen einzugeben (mit einem Datum nach dem 01.01.2008):
+       </para>
+       <para>Vermögenskonto | Bewegungskonto | Betrag</para>
+       <para>* 0110 Bargeld - Person A | 2000 Verpflegung | -35,00</para>
+       <para>* 0110 Bargeld - Person A | 4020 Person A - Freizeit | -22,20</para>
+       <para>* 0110 Bargeld - Person A | 3520 Auto - Benzin | -40,00</para>
+       <para>* 0120 Bargeld - Perosn B | 0200 Girokonto | 1.000,00</para>
+       <para>* 0120 Bargeld - Person B | 2000 Verpflegung | -320,10</para>
+       <para>* 0120 Bargeld - Person B | 5010 Person B - Kleidung | -278,00</para>
+       <para>* 0200 Girokonto | 1000 Gehalt | 2.600,00</para>
+       <para>* 0200 Girokonto | 3010 Heim - Miete | -520,00</para>
+       <tip>
+       Tipp: Die vierte Buchung ist eine Vermögensumlage, Person B hebt 1.000,00 vom Girokonto ab. Es wäre auch möglich gewesen die Transaktion aus Sicht des Girokontos zu tätigen, indem man Vermögens- und Bewegungskonto vertauscht und einen negativen Betrag eingibt.
+       </tip>
+       <tip>
+       Tipp: Aus Komfort ist es möglich Ausgaben ohne negativem Vorzeichen einzugeben (siehe <ref module="fima" entry="fima-options">Oberflächen-Einstellungen: Ausgaben mit negativem Vorzeichen eingeben?</ref>). Jedenfalls betrifft der eingegeben Betrag bei der vierten Transaktion (Vermögensumlage) stets das ausgewählte Vermögenskonto.
+       </tip>
+       <tip>
+       Tipp: Kontrollieren Sie die Endbeträge der Vermögenskonten mit der Auswertung <ref module="fima" entry="reports-details">Vermögensübersicht</ref> (mit kumulierten Werten).
+       </tip>
+       <tip>
+       Tipp: Verfolgen und analysieren Sie Ihre Einnahmen und Ausgaben mit den Auswertungen <ref module="fima" entry="reports-details">Kontoübersicht und Analyse</ref>. Benutzen Sie die Auswertungen <ref module="fima" entry="reports-details">Periodenübersicht und Trend</ref> zur Analyse und Ãœberprüfung der Entwicklung Ihrer Kosten.
+       </tip>
+</entry>
+
+<entry id="exemplar-budget">
+       <title>Musterbeispiel: 4) Budget</title>
+       <para>
+       Um Einnahmen und Ausgaben zu planen und analysieren erstellen wir ein Budget für 2008 (normalerweise bevor Buchungen eingegeben werden). Es existieren zwei Möglichkeiten ein Budget zu erstellen:
+       </para>
+       <para>
+       A) Erstellen Sie Ihr Budget ganz neu, indem Sie händisch Buchungen für jeden Monat eingeben. Aus Komfort empfielt es sich nur Hauptkonten zu verwenden. Benutzen Sie die Funktion <ref module="fima" entry="postings-addedit">Auto-Füllen</ref> um die Werte der ersten Zeile in die nächsten elf für das gesamte Jahr zu kopieren. Es ist erforderlich, dass Sie Budget-Buchungen für jedes Konto getrennt auf diese Weise eingeben.
+       </para>
+       <para>
+       B) Erstellen Sie Ihr Budget basierend auf den Einnahmen und Ausgaben des Vorjahrs (sofern verfügbar). Benutzen Sie die Funktion <ref module="fima" entry="postings-transfer">Buchungen transferieren</ref> mit folgenden Parametern:
+       </para>
+       <para>* Transfer von: Ist-Abrechnung</para>
+       <para>* Zeitraum von: 2007 - Alle</para>
+       <para>* Ursprüngliche Buchungen behalten: ja</para>
+       <para>* Konten summieren: Summieren durch Kombination.</para>
+       <para>* Transfer nach: Budget</para>
+       <para>* Zeitraum bis: 2008 - Alle</para>
+       <para>* Existierende Buchungen löschen: ja</para>
+       <tip>
+       Tipp: Vergleichen Sie Budgetwerte mit der Ist-Abrechung mithilfe der Auswertungen <ref module="fima" entry="reports-details">Gesamtübersicht, Kontoübersicht oder Analyse</ref>.
+       </tip>
+</entry>
+
+<entry id="exemplar-forecast">
+       <title>Musterbeispiel: 5) Vorschau</title>
+       <para>
+       Da ein Budget normalerweise nach der Erstellung unverändert bleibt, wollen wir eine Vorschau zum Anpassen. Es existieren zwei Möglichkeiten eine Vorschau zu erstellen:
+       </para>
+       <para>
+       A) Erstellen Sie Ihre Vorschau ganz neu (siehe <ref module="fima" entry="exemplar-budget">Musterbeispiel: Budget</ref>).
+       </para>
+       <para>
+       B) Erstellen Sie Ihre Vorschau als Kopie Ihres Budgets mittels der Funktion <ref module="fima" entry="postings-transfer">Buchungen transferieren</ref> mit folgenden Parametern:
+       </para>
+       <para>* Transfer von: Budget</para>
+       <para>* Zeitraum von: 2008 - Alle</para>
+       <para>* Ursprüngliche Buchungen behalten: ja</para>
+       <para>* Konten summieren: Nicht summieren.</para>
+       <para>* Transfer nach: Vorschau</para>
+       <para>* Zeitraum bis: 2008 - Alle</para>
+       <para>* Existierende Buchungen löschen: ja</para>
+       <tip>
+       Tipp: Vergleichen Sie Vorschauwerte mit dem Budget oder der Ist-Abrechnung mithilfe der Auswertungen <ref module="fima" entry="reports-details">Gesamtübersicht, Kontoübersicht oder Analyse</ref>.
+       </tip>
+</entry>
+
+</help>
diff --git a/fima/locale/en_US/help.xml b/fima/locale/en_US/help.xml
new file mode 100644 (file)
index 0000000..815d107
--- /dev/null
@@ -0,0 +1,407 @@
+<?xml version='1.0'?>
+<!-- $Horde: fima/locale/en_US/help.xml,v 1.0 2008/09/11 19:31:38 trt Exp $ -->
+<help>
+
+<entry id="fima-overview">
+    <title>Fima: Overview</title>
+    <heading>What is Fima?</heading>
+    <para>
+    Fima stands for Finance Manager and is a double entry based ledger. It is a fully flexibel tool for managing your finances including keeping track of your expenses, incomes and assets. It offers creation of budgets, forecasts and a range of different reports for analysis.
+    </para>
+    <heading>Main features:</heading>
+    <para>* free and flexible structure of the table of accounts</para>
+    <para>* management and tracking of assets</para>
+    <para>* independent posting types: actual, budget, forecast</para>
+    <para>* flexible and detailled reports using various parameters</para>
+    <para>* graphical report charts</para>
+</entry>
+
+<entry id="fima-options">
+       <title>Fima: Options</title>
+       <heading>Active Configuration</heading>
+       <para>* Your active ledger: select your current ledger</para>
+       <para>* Your active posting type: select the current <ref module="fima" entry="postings-postingtypes">posting type</ref></para>
+       <para>* Closed by period: select the end of the period until which adding and editing postings will be locked; select &quot;None&quot; to keep all postings changeable</para>
+       <heading>Interface Options</heading>
+       <para>* Postings per page in the list view: enter the number of postings to display per page</para>
+       <para>* When displaying the posting, which page do you want to start on: first or last page</para>
+       <para>* Sort postings by: select the column which to sort postings by</para>
+       <para>* Then: select an alternative column which to sort postings by</para>
+       <para>* Sort direction: ascending or descending</para>
+       <para>* Select the format for wildcards for text search: DOS wildcards (* and ?), SQL wildcards (% and _) or none</para>
+       <para>* Select the format for amounts: select your preferred notation</para>
+       <para>* Enter expenses with negative sign: choose whether to omit the negative sign when entering expenses</para>
+       <para>* Do you want to confirm deleting postings: choose whether you&apos;d like to confirm deleting postings</para>
+       <para>* Select the canvas size for chart reports: the dimensions of the generated image in pixel</para>
+</entry>
+
+<entry id="accounts-overview">
+       <title>Accounts: Overview</title>
+       <para>
+       The table of accounts is structured in three levels: 
+       </para>
+       <heading>Account Types</heading>
+       <para>
+       Account types are the general aggregation of different accounts. Each account is assigned one single type. There are three different account types:
+       </para>
+       <para>* income (eg. salary, prizes, donations, etc.)</para>
+       <para>* expense (eg. food, rental, clothes, etc.)</para>
+    <para>* asset (eg. cash, credit card, bankbook, etc.)</para>
+    <para>
+       It is necessary to specify an asset account for each posting transaction. This way it is possible to track your assets and money transfers as well (postings from an asset account to another one).
+       </para>
+       <tip>
+       Tip: If you are interested in your incomes and expenses only and don&apos;t want to manage any assets, create only one single asset account that will be automatically used for all postings.
+       </tip>
+       <heading>Account groups, Main Accounts, Subaccounts</heading>
+       <para>
+       An account is an aggregation of similar types of income, expense or asset positions. An account is defined by its four digit number, a name and an account type. The account number is devided into two parts: XXYY, where XX refers to the account group and YY to the subaccount (eg. in the account group 12: 1200 is the main account, 1230 and 1245 are both subaccounts). It is possible to create 100 account groups each having 100 accounts, whereas the usage of subaccounts is not necessary.
+       </para>
+       <para>
+       Additionally it is possible to save a long description for each account and select whether postings for this account should be marked e.o. by default (see <ref module="fima" entry="postings-overview">Postings Overview</ref>). Also an account may be closed in order to prevent new postings but keep existing ones.
+       </para>
+       <tip>
+       Tip: When using subaccounts avoid posting to the main account XX00, since it won&apos;t be possible to identify these postings in reports when aggregated to account groups. It is recommended to create a separate subaccount for general postings that don&apos;t fit into any subaccount (eg. XX99).
+       </tip>
+</entry>       
+
+<entry id="accounts-delete">
+       <title>Accounts: Deleting an Account</title>
+       <para>
+       When deleting an account you need to specify how to handle postings:
+       </para>
+       <para>* Delete postings: delete all existing postings that were posted to the specific account</para>
+       <para>* Shift postings: move all existing postings to another account of the same account type</para>
+       <para>
+       Also you need to specify how to handle potential subaccounts and their postings:
+       </para>
+       <para>* Keep subaccounts and postings: don&apos;t do anything</para>
+       <para>* Delete subaccounts and postings: delete all existing subaccounts and postings too</para>
+       <para>* Delete subaccounts and shift postings: delete all existing subaccounts and previously shift any existing postings to another account of the same account type</para>
+</entry>
+
+<entry id="postings-overview">
+       <title>Postings: Overview</title>
+       <para>
+       A posting record has of the following attributes:
+       </para>
+       <para>* Posting types: see <ref module="fima" entry="postings-postingtypes">Posting Types</ref></para>
+       <para>* Date: date of the posting (day, month, year)</para>
+       <para>* Asset account: specifying the asset account for each posting is necessary to track your assets</para>
+       <para>* Posting account: the account for the transaction, usually of the type income or expense; when shifting assets (eg. draw cash from an bank account) you need to choose the appropriate asset account as posting account</para>
+       <para>* Description: it is possible to enter a short description for each posting (max. 255 characters)</para>
+       <para>* Amount: positive amounts increase and negative amounts decrease the chosen asset account</para>
+       <para>* e.o.: postings may be marked extraordinary (very large, not planned, ...)</para>
+</entry>
+
+<entry id="postings-postingtypes">
+       <title>Postings: Posting Types</title>
+       <para>
+       Each posting is assigned one single type. There are three different posting types:
+       </para>
+       <heading>Actual</heading>
+       <para>
+       Actual postings constist of current incomes and expenses which can be analyzed using different reports. It reflects your actual financial situation. 
+       </para>
+       <tip>
+       Tip: Always check for asset differences before starting entering new postings. Book any differences to a specific account to guarantee at least correct asset accounts. Also check any bank account statements for missing postings.
+       </tip>
+       <tip>
+       Tip: Try to enter your postings on a regular basis. Always ask for a sales receipt or write down the expense on a piece of paper, so you won&apos;t forget any expenses when not entereing postings immediately.
+       </tip>
+       <heading>Budget</heading>
+       <para>
+       Using a budget is optional and offers control of your future financial development as well as analyzing your former financial objectives. This way it is possible to identify your financial scope early.
+       </para>
+       <tip>
+       Tip: Try to follow your main budget objectives. However, don&apos; stick to them at all cost, since there will be incidents that won&apos;t be included in your budget.
+       </tip>
+       <tip>
+       Tip: If you use a detailed table of accounts, create your budget based on main accounts instead of subaccounts since it would be very time-consuming otherwise.
+       </tip>
+       <tip>
+       Tip: Enter any differences between your actual and budget to your budget. This way also your budget assets are up-to-date.
+       </tip>
+       <tip>
+       Tip: It is possible to create your budget based on the postings of the last year by copying and optionally summarize them using the <ref module="fima" entry="postings-transfer">Transfer Postings</ref> functionality and adapting it afterwards.
+       </tip>
+       <heading>Forecast</heading>
+       <para>
+       After creating a budget it usually won&apos;t change. All further adaptions can be included in the forecast which therefore is an updated budget.
+       </para>
+       <tip> 
+       Tip: Check for any differences between your actual and budget/forecast result regularly and adapt your future forecast.
+       </tip>
+       <tip>
+       Tip: Enter any differences between your actual and forcast to your budget. This way also your forecast assets are up-to-date.
+       </tip>
+       <tip>
+       Tip: It is possible to create your forecast by copying your budget using the <ref module="fima" entry="postings-transfer">Transfer Postings</ref> functionality and afterwards adapting it afterwards.
+       </tip>
+</entry>
+
+<entry id="postings-addedit">
+       <title>Postings: Add/Edit Postings</title>
+       <para>
+       When adding new postings the date and asset account will be copied from the entry above for convenience, but may be changed later on.
+       </para>
+       <para>
+       Expenses usually are negative amounts, but may be entered without negative sign for convenience (see <ref module="fima" entry="fima-options">Interface Options: Enter expenses with negative sign?</ref>). However, when shifting assets (transfer from one asset account to another one) the entered value always applies to the selected asset account (eg. -10 means a decrease of the selected asset account by 10 and subsequently an increase by 10 of the asset account selected as posting account).
+       </para>
+       <tip>
+       Tip: When entering new postings, it is possible to select accounts by simply typing the account number on the keyboard instead of browsing through the drop down field. In a short time you will know the most used account numbers and it will save you a lot of time entering new postings.
+       </tip>
+       <tip>
+       Tip: When entering new postings, it is possible to execute mathematical operations in the amount field. Simply type the mathematical expression (eg. 3+2*5) and the result will automatically be calculated.
+       </tip>
+       <tip>
+       Tip: In order to prevent adding or editing postings with a date in the past unintentionally, enter a closing period (see <ref module="fima" entry="fima-options">Options: Active Configuration: Closed by period</ref>).
+       </tip>
+       <tip>
+       Tip: When entering new postings for <ref module="fima" entry="postings-postingtypes">budget or forecast</ref>, it is possible to automatically create a set of similar postings for each month of a specific year. In the first row enter any date in the desired year, choose an asset and posting account and enter a description (optionally) and amount. Then press the button &quot;Autofill&quot; to copy the entered values down for alle months.
+       </tip>
+</entry>
+
+<entry id="postings-shift">
+       <title>Postings: Shift Postings</title>
+       <para>
+       Shift several selected postings in order to move them to another posting type or to change the asset or posting account all at once.
+       </para>
+</entry>
+
+<entry id="postings-transfer">
+       <title>Postings: Transfer Postings</title>
+       <para>
+       Transfer all postings of a specific posting type within a specific period to another posting type using the following options:
+       </para>
+       <para>* Transfer from: source posting type where to transfer posting from</para>
+       <para>* Period from: source period of posting to transfer from</para>
+       <para>* Keep original Postings: check to copy selected postings, leave unchecked to move postings</para>
+       <para>* Don't summarize: leave postings unchanged</para>
+       <para>* Summarize by combining: summarize postings by grouping according to the combination of asset and posting account</para>
+       <para>* Summarize by posting against: summarize postings by grouping by asset and posting account seperately, each posting against a specific (dummy) account</para>
+       <para>* Transfer to: destination posting type where to transfer posting to</para>
+       <para>* Period to: destination period of posting to transfer to</para>
+       <para>* Delete existing Postings: previously delete any existing postings of the destination posting type within the destination period</para>
+       <tip>
+       Tip: You may create a <ref module="fima" entry="postings-postingtypes">budget (or forecast)</ref> by transfering existing posting from actual (or budget). Don&apos;t forget to keep the original posting. It is recommended to summarize postings for a budget (or forecast) to have postings on main account level only. This way it is easier to make adaptions later.
+       </tip>
+       <tip>
+       Tip: It is possible to transfer all postings of a year at once. Select &quot;All&quot; as source and destination month in order to keep the posting month unchanged.
+       </tip>
+</entry>
+
+<entry id="postings-search">
+       <title>Postings: Search Postings</title>
+       <para>
+       When searching postings it is possible to use the following filters:
+       </para>
+       <para>* Posting Type: posting type to filter</para>
+       <para>* Date Start: start of filter period</para>
+       <para>* Date End: end of filter period</para>
+       <para>* Asset Accounts: postings with the selected account(s) as asset or posting account (when shifting assets) will be filtered</para>
+       <para>* Posting Accounts: postings with the selected account(s) as posting account will be filtered</para>
+       <para>* Description: you may use wildcards when search the description (see <ref module="fima" entry="fima-options">Interface Options: Select the format for wildcards for text search</ref>).</para>
+       <para>* Amount Start: minimum amount</para>
+       <para>* Amount End: maximum amount</para>
+       <para>* e.o. Postings: filter all or none e.o. postings; leave empty to get all postings</para>
+       <tip>
+       Tip: It is possible to select several asset and posting accounts by holding the shift or control key.
+       </tip>
+       <tip>
+       Tip: When filtering for amounts remember to enter expenses with a negative sign.
+       </tip>
+       <tip>
+       Tip: A search query is saved for the current session until cleaned or a new search is executed. An indicator for an active search query is the trash bin icon located in the header bar.
+       </tip>
+</entry>
+
+<entry id="reports-overview">
+       <title>Reports: Overview</title>
+       <para>
+       When creating reports it is possible to use the following parameters:
+       </para>
+       <para>* Report: different <ref module="fima" entry="reports-details">reports</ref></para>
+       <para>* Display: select posting type (some reports use the first value only, other use all values)</para>
+       <para>* Period: start and end of report period</para>
+       <para>* Reference: start and end of report reference period (when using reference as posting type for display)</para>
+       <para>* Accounts: postings with the selected account(s) as posting account will be reported</para>
+       <para>* Cumulate: summarize partial results within the selected period</para>
+       <para>* Subaccounts: additionally show subaccounts instead of main accounts only</para>
+       <para>* Null Rows: display rows containing no results</para>
+       <para>* Yearly: group results by years instead of months</para>
+       <para>* Chart: display a report graph instead of a table view</para>
+       <para>
+       All reports use only a set of parameters. See <ref module="fima" entry="reports-details">report details</ref> for a description of each report and used parameters.
+       </para>
+</entry>
+
+<entry id="reports-details">
+       <title>Reports: Details</title>
+
+       <heading>General Overview</heading>
+       <para>* Description: account types, totals and asset result by posting types</para>
+       <para>* Parameters: Display (all), Period, (Reference), Accounts, Chart</para>
+       <para>* Chart: bar chart</para>
+       
+       <heading>Period Overview</heading>
+       <para>* Description: account types, totals and asset result by period</para>
+       <para>* Paramaters: Display (first), Period, (Reference), Accounts, Cumulate, Null Rows, Yearly, Chart</para>
+       <para>* Chart: line chart</para>
+       
+       <heading>Account Overview</heading>
+       <para>* Description: posting types by period</para>
+       <para>* Parameters: Display (all), Period, (Reference), Accounts, Cumulate, Null Rows, Yearly, Chart</para>
+       <para>* Chart: line chart</para>
+       
+       <heading>Asset Overview</heading>
+       <para>* Description: asset accounts by period</para>
+       <para>* Parameters: Display (first), Period, (Reference), Cumulate, Subaccounts, Null Rows, Yearly, Chart</para>
+       <para>* Chart: line chart, top 5</para>
+
+       <heading>Analysis</heading>
+       <para>* Description: posting types by accounts</para>
+       <para>* Parameters: Display (all), Period, (Reference), Accounts, Subaccounts, Null Rows, Chart</para>
+       <para>* Chart: pie chart, top 5 (rest grouped) by account type</para>
+
+       <heading>Trend</heading>
+       <para>* Description: period by accounts</para>
+       <para>* Parameters: Display (first), Period, (Reference), Accounts, Subaccounts, Null Rows, Yearly, Chart</para>
+       <para>* Chart: line chart, top 5 by account type</para>
+</entry>
+
+<entry id="exemplar-overview">
+       <title>Exemplar: Overview</title>
+       <para>
+       This exemplar demonstrates how to use the main features of Fima. Follow the instructions step by step in the given order.
+       </para>
+</entry>
+
+<entry id="exemplar-accounts">
+       <title>Exemplar: 1) Accounts</title>
+       <heading>Asset</heading>
+       <para>* 0100 Cash</para>
+       <para>* * 0110 Person A</para>
+       <para>* * 0120 Person B</para>
+       <para>* 0200 Giro</para>
+       <para>* 0300 Bankbook</para>
+       <para>* 0400 Life Assurance</para>
+       <heading>Income</heading>
+       <para>* 0000 Opening Balance</para>
+       <para>* 1000 Salary</para>
+       <para>* 1900 Other Income</para>
+       <para>* * 1910 Subsidy</para>
+       <para>* * 1999 Miscellaneous</para>
+       <heading>Expense</heading>
+       <para>* 2000 Food</para>
+       <para>* 3000 Home</para>
+       <para>* * 3010 Rental</para>
+       <para>* * 3020 Electricity</para>
+       <para>* * 3030 Furniture</para>
+       <para>* 3500 Car</para>
+       <para>* * 3510 Insurance</para>
+       <para>* * 3520 Fuel</para>
+       <para>* 4000 Person A</para>
+       <para>* * 4010 Clothes</para>
+       <para>* * 4020 Hobby</para>
+       <para>* 5000 Person B</para>
+       <para>* * 5010 Clothes</para>
+       <para>* * 5020 Hobby</para>
+       <para>* 9900 Other Expense</para>
+       <tip>
+       Tip: The table of accounts shown above is only a small example. In reality there are a lot of other things to consider (eg. sales, donations, lottery, interest, TV/Internet, electrical devices, refurbishment, repairings, housekeeping, sanitary products, drugs, pets, coiffeur, car parking, road tax, car mechanic, spare parts, education, telephony, parties, music/video, games, sports, books, smoking, vacation, presents, taxes, etc.)
+       </tip>
+</entry>
+
+<entry id="exemplar-opening">
+       <title>Exemplar: 2) Opening</title>
+       <para>
+       Before starting to enter regular postings we list our assets and post them against the account 0000 Opening Balance (eg. on the date 2008-01-01):
+       </para>
+       <para>Asset Account | Posting Account | Amount</para>
+       <para>* 0110 Cash - Person A | 0000 Opening Balance | 133.50</para>
+       <para>* 0120 Cash - Person B | 0000 Opening Balance | 420.30</para>
+       <para>* 0200 Giro | 0000 Opening Balance | -3,412.50</para>
+       <para>* 0300 Bankbook | 0000 Opening Balance | 6,850.20</para>
+       <para>* 0400 Life Assurance | 0000 Opening Balance | 10,500.00</para>   
+       <para>In total our asset result is therefore 14,491.50.</para>
+       <tip>
+       Tip: Use a seperate account of type income for your opening balance. This way reports show correct amounts and assets can be tracked.
+       </tip>
+</entry>
+
+<entry id="exemplar-postings">
+       <title>Exemplar: 3) Postings</title>
+       <para>
+       After doing our <ref module="fima" entry="exemplar-opening">opening balance</ref> let&apos;s start entering some postings (past the date 2008-01-01):
+       </para>
+       <para>Asset Account | Posting Account | Amount</para>
+       <para>* 0110 Cash - Person A | 2000 Food | -35.00</para>
+       <para>* 0110 Cash - Person A | 4020 Person A - Hobby | -22.20</para>
+       <para>* 0110 Cash - Person A | 3520 Car - Fuel | -40.00</para>
+       <para>* 0120 Cash - Perosn B | 0200 Giro | 1,000.00</para>
+       <para>* 0120 Cash - Person B | 2000 Food | -320.10</para>
+       <para>* 0120 Cash - Person B | 5010 Person B - Clothes | -278.00</para>
+       <para>* 0200 Giro | 1000 Salary | 2,600.00</para>
+       <para>* 0200 Giro | 3010 Home - Rental | -520.00</para>
+       <tip>
+       Tip: The fourth posting is a transaction for shifting assets, person B withdraws 1,000.00 from the giro account. It would have also been possible to look on the transaction from the perspective of the giro account by switching asset and posting account and enter a negative amount.
+       </tip>
+       <tip>
+       Tip: It is possible to enter expenses without negative sign for convenience (see <ref module="fima" entry="fima-options">Interface Options: Enter expenses with negative sign?</ref>). However, with the fourth transaction (shifting assets) the entered value always applies to the selected asset account.
+       </tip>
+       <tip>
+       Tip: Control the final amounts of asset accounts by using the report <ref module="fima" entry="reports-details">Asset Overview</ref> (with cumulated values).
+       </tip>
+       <tip>
+       Tip: Track and analyse your incomes and expenses using the reports <ref module="fima" entry="reports-details">Account Overview and Analysis</ref>. Use the reports <ref module="fima" entry="reports-details">Period Overview and Trend</ref> to analyse and check the development of costs.
+       </tip>
+</entry>
+
+<entry id="exemplar-budget">
+       <title>Exemplar: 4) Budget</title>
+       <para>
+       In order to plan and analyze incomes and expenses, we create a budget in 2008 (usually before entering any postings). There are two possibilities for creating a budget:
+       </para>
+       <para>
+       A) Create your budget from scratch by entering postings manually for each month. For convenience it is recommended to use only main accounts. Use the function <ref module="fima" entry="postings-addedit">Autofill</ref> to copy the values from the first line to next eleven for the whole year. It is necessary to enter budget postings for each account seperately this way.
+       </para>
+       <para>
+       B) Create your budget based on incomes and expenses from the last year (if any). Use the function <ref module="fima" entry="postings-transfer">Transfer Postings</ref> with the following parameters:
+       </para>
+       <para>* Transfer from: Actual</para>
+       <para>* Period from: 2007 - All</para>
+       <para>* Keep original Postings: yes</para>
+       <para>* Summarize Accounts: Summarize by combining.</para>
+       <para>* Transfer to: Budget</para>
+       <para>* Period to: 2008 - All</para>
+       <para>* Delete existing postings: yes</para>
+       <tip>
+       Tip: Compare budget and actual amounts using the reports <ref module="fima" entry="reports-details">General Overview, Account Overview or Analysis</ref>.
+       </tip>
+</entry>
+
+<entry id="exemplar-forecast">
+       <title>Exemplar: 5) Forecast</title>
+       <para>
+       Since a budget usually remains untouched after creation, we want a forecast to adapt. There are two possibilities for ceating a forecast:
+       </para>
+       <para>
+       A) Create your forecast from scratch (see <ref module="fima" entry="exemplar-budget">Exemplar: Budget</ref>).
+       </para>
+       <para>
+       B) Create your forecast by copying the budget using the function <ref module="fima" entry="postings-transfer">Transfer Postings</ref> with the following parameters:
+       </para>
+       <para>* Transfer from: Budget</para>
+       <para>* Period from: 2008 - All</para>
+       <para>* Keep original Postings: yes</para>
+       <para>* Summarize Accounts: Don&apos;t summarize.</para>
+       <para>* Transfer to: Forecast</para>
+       <para>* Period to: 2008 - All</para>
+       <para>* Delete existing postings: yes</para>
+       <tip>
+       Tip: Compare forecast and budget or actual amounts using the reports <ref module="fima" entry="reports-details">General Overview, Account Overview or Analysis</ref>.
+       </tip>
+</entry>
+
+</help>
diff --git a/fima/po/README b/fima/po/README
new file mode 100644 (file)
index 0000000..a985e94
--- /dev/null
@@ -0,0 +1 @@
+see horde/po/README
diff --git a/fima/po/de_DE.po b/fima/po/de_DE.po
new file mode 100644 (file)
index 0000000..b5b707d
--- /dev/null
@@ -0,0 +1,1328 @@
+# German translations for Fima package
+# German messages for Fima.
+# Copyright (C) 2008 Horde Project
+# This file is distributed under the same license as the Fima package.
+# Automatically generated, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Fima 1.0\n"
+"Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+"POT-Creation-Date: 2009-03-23 07:41+0100\n"
+"PO-Revision-Date: 2008-09-04 10:56+0200\n"
+"Last-Translator: Thomas Trethan <thomas@trethan.net>\n"
+"Language-Team: i18n@lists.horde.org\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: report.php:97 report.php:98 report.php:99 report.php:100 report.php:101
+#: report.php:102 report.php:103
+msgid "%"
+msgstr "%"
+
+#: postings.php:270
+#, php-format
+msgid "%d postings not saved."
+msgstr "%d Buchungen wurden nicht gespeichert."
+
+#: templates/postings/shift.inc:5
+#, php-format
+msgid "%d selected Postings"
+msgstr "%d ausgewählte Buchungen"
+
+#: postings.php:587
+#, php-format
+msgid "%s"
+msgstr "%s"
+
+#: lib/Report/Trend.php:113 lib/Report/Analysis.php:125
+#, php-format
+msgid "%s Result"
+msgstr "%s Ergebnis"
+
+#: report.php:95
+#, php-format
+msgid "%s [%s - %s - %s]"
+msgstr "%s [%s - %s - %s]"
+
+#: data.php:207
+#, php-format
+msgid "%s successfully imported"
+msgstr "%s erfolgreich importiert"
+
+#: postings.php:617
+#, php-format
+msgid "%s to %s of %s Postings"
+msgstr "%s bis %s von %s Buchungen"
+
+#: lib/Fima.php:728 lib/Forms/DeleteLedger.php:85
+#, php-format
+msgid "%s's Ledger"
+msgstr "%ss Buch"
+
+#: config/prefs.php.dist:148
+msgid "-12 345 678,90"
+msgstr "-12 345 678,90"
+
+#: config/prefs.php.dist:149
+msgid "-12'345'678.90"
+msgstr "-12'345'678.90"
+
+#: config/prefs.php.dist:147
+msgid "-12,345,678.90"
+msgstr "-12,345,678.90"
+
+#: config/prefs.php.dist:146
+msgid "-12.345.678,90"
+msgstr "-12.345.678,90"
+
+#: config/prefs.php.dist:179
+msgid "1024 x 768 Pixel"
+msgstr "1024 x 768 Pixel"
+
+#: config/prefs.php.dist:180
+msgid "1600 x 1200 Pixel"
+msgstr "1600 x 1200 Pixel"
+
+#: config/prefs.php.dist:177
+msgid "400 x 300 Pixel"
+msgstr "400 x 300 Pixel"
+
+#: config/prefs.php.dist:178
+msgid "800 x 600 Pixel"
+msgstr "800 x 600 Pixel"
+
+#: templates/postings/posting_headers.inc:21 templates/postings/shift.inc:21
+msgid "A_sset Account"
+msgstr "Vermögenskonto"
+
+#: account.php:209
+#, php-format
+msgid "Access denied deleting account from %s."
+msgstr "Zugriff verweigert beim Löschen des Kontos von %s."
+
+#: account.php:175
+msgid "Access denied deleting account."
+msgstr "Zugriff verweigert beim Löschen des Kontos."
+
+#: accounts.php:29
+msgid "Access denied deleting all accounts and postings."
+msgstr "Zugriff verweigert beim Löschen aller Konten und Buchungen."
+
+#: postings.php:289
+#, php-format
+msgid "Access denied deleting postings from %s."
+msgstr "Zugriff verweigert beim Löschen der Buchungen von %s."
+
+#: account.php:73
+msgid "Access denied editing account."
+msgstr "Zugriff verweigert beim Bearbeiten des Kontos."
+
+#: account.php:27
+#, php-format
+msgid "Access denied on account: %s"
+msgstr "Zugriff verweigert für das Konto: %s"
+
+#: accounts.php:21
+#, php-format
+msgid "Access denied on accounts: %s"
+msgstr "Zugriff verweigert für die Konten: %s"
+
+#: account.php:106
+#, php-format
+msgid "Access denied saving account to %s."
+msgstr "Zugriff verweigert beim Speichern des Kontos in %s."
+
+#: postings.php:174
+#, php-format
+msgid "Access denied saving postings to %s."
+msgstr "Zugriff verweigert beim Speichern der Buchungen in %s."
+
+#: postings.php:320
+#, php-format
+msgid "Access denied shifting postings in %s."
+msgstr "Zugriff verweigert beim Umbuchen der Buchungen in %s."
+
+#: postings.php:355 postings.php:359
+#, php-format
+msgid "Access denied transfering postings in %s."
+msgstr "Zugriff verweigert beim Transfer der Buchungen in %s."
+
+#: data.php:40 lib/Report/Trend.php:71 lib/Report/Analysis.php:75
+msgid "Account"
+msgstr "Konto"
+
+#: config/report.php.dist:11
+msgid "Account Overview"
+msgstr "Kontoübersicht"
+
+#: account.php:77 account.php:179
+msgid "Account not found."
+msgstr "Konto nicht gefunden."
+
+#: lib/Block/tree_menu.php:20 templates/reports/reports.inc:34
+msgid "Accounts"
+msgstr "Konten"
+
+#: config/prefs.php.dist:14
+msgid "Active Configuration"
+msgstr "Aktive Konfiguration"
+
+#: lib/Fima.php:472 config/prefs.php.dist:52
+msgid "Actual"
+msgstr "Ist-Abrechnung"
+
+#: postings.php:137 lib/Block/tree_menu.php:18
+msgid "Add Postings"
+msgstr "Neue Buchungen"
+
+#: lib/Fima.php:759
+msgid "Add _Postings"
+msgstr "Neue _Buchungen"
+
+#: templates/postings/transfer.inc:14 templates/postings/transfer.inc:53
+#: templates/postings/navbar.inc:9
+msgid "All"
+msgstr "Alle"
+
+#: templates/postings/posting_headers.inc:36
+msgid "Amo_unt"
+msgstr "Be_trag"
+
+#: data.php:42 config/prefs.php.dist:98 config/prefs.php.dist:112
+msgid "Amount"
+msgstr "Betrag"
+
+#: config/report.php.dist:13
+msgid "Analysis"
+msgstr "Analyse"
+
+#: templates/accounts/accounts.inc:12
+msgid ""
+"Are you sure you wish to PERMANENTLY delete all accounts and all postings?"
+msgstr ""
+"Sind Sie sicher, dass Sie alle Konten und Buchungen UNWIDERRUFLICH löschen "
+"wollen?"
+
+#: templates/postings/javascript_list.inc:45
+msgid "Are you sure you wish to PERMANENTLY delete these postings?"
+msgstr ""
+"Sind Sie sicher, dass Sie diese Buchungen UNWIDERRUFLICH löschen wollen?"
+
+#: config/prefs.php.dist:123
+msgid "Ascending"
+msgstr "Aufsteigend"
+
+#: lib/Fima.php:448 lib/Report/AssetOverview.php:68
+msgid "Asset"
+msgstr "Vermögen"
+
+#: data.php:39 config/prefs.php.dist:96 config/prefs.php.dist:110
+msgid "Asset Account"
+msgstr "Vermögenskonto"
+
+#: config/report.php.dist:12
+msgid "Asset Overview"
+msgstr "Vermögensübersicht"
+
+#: lib/Report/GeneralOverview.php:63 lib/Report/GeneralOverview.php:98
+#: lib/Report/PeriodOverview.php:78 lib/Report/AssetOverview.php:92
+#: lib/Block/summary.php:77
+msgid "Asset Result"
+msgstr "Vermögen"
+
+#: templates/postings/actions.inc:17
+msgid "Autofill"
+msgstr "Auto-Füllen"
+
+#: lib/Block/summary.php:35
+msgid "Block title"
+msgstr "Block Titel"
+
+#: lib/Fima.php:474 config/prefs.php.dist:54
+msgid "Budget"
+msgstr "Budget"
+
+#: data.php:25
+msgid "CSV"
+msgstr "CSV"
+
+#: templates/reports/reports.inc:80
+msgid "C_umulate"
+msgstr "K_umulieren"
+
+#: lib/Forms/DeleteLedger.php:44 lib/Forms/DeleteLedger.php:50
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: ledgers/index.php:35 templates/ledgers_list.php:33
+msgid "Change Permissions"
+msgstr "Rechte Ã¤ndern"
+
+#: config/prefs.php.dist:22
+msgid "Change the display and input options."
+msgstr "Anzeige- und Eingabe-Einstellungen Ã¤ndern."
+
+#: templates/postings/posting_headers.inc:60
+msgid "Check _All/None"
+msgstr "_Alle/Keine auswählen"
+
+#: config/prefs.php.dist:15
+msgid "Choose your active Ledger and Posting Type."
+msgstr "Wählen Sie Ihr aktives Buch und Ihren aktiven Datenkreis aus."
+
+#: templates/search/search.inc:107
+msgid "Clear Form"
+msgstr "Formular zurücksetzen"
+
+#: templates/search/search.inc:8 templates/postings/header.inc:10
+msgid "Clear Search Query"
+msgstr "Suchabfrage zurücksetzen"
+
+#: lib/Forms/account.php:47
+msgid "Closed"
+msgstr "Abgeschlossen"
+
+#: templates/prefs/closedperiodselect.inc:3
+msgid "Closed by period:"
+msgstr "Abgeschlossen mit Zeitraum:"
+
+#: templates/data/export.inc:12 templates/data/import.inc:13
+msgid "Comma separated values"
+msgstr "Komme getrennte Werte"
+
+#: lib/Forms/CreateLedger.php:38
+msgid "Create"
+msgstr "Erstellen"
+
+#: lib/Forms/CreateLedger.php:33
+msgid "Create Ledger"
+msgstr "Buch erstellen"
+
+#: templates/accounts/accounts.inc:46
+msgid "Create a New Account"
+msgstr "Neues Konto erstellen"
+
+#: templates/ledgers_list.php:8
+msgid "Create a new Ledger"
+msgstr "Neues Buch erstellen"
+
+#: templates/postings/edit.inc:52
+msgid "Current Result"
+msgstr "Aktuelles Ergebnis"
+
+#: config/prefs.php.dist:134
+msgid "DOS (* and ?)"
+msgstr "DOS (* und ?)"
+
+#: templates/postings/posting_headers.inc:16
+msgid "Da_te"
+msgstr "Da_tum"
+
+#: data.php:38 config/prefs.php.dist:95 config/prefs.php.dist:109
+msgid "Date"
+msgstr "Datum"
+
+#: lib/Forms/account.php:84 lib/Forms/DeleteLedger.php:44 ledgers/index.php:36
+#: templates/ledgers_list.php:35 templates/accounts/accounts.inc:43
+#: templates/accounts/accounts.inc:45 templates/postings/actions.inc:9
+msgid "Delete"
+msgstr "Löschen"
+
+#: lib/Forms/DeleteLedger.php:39 templates/accounts/accounts.inc:45
+#, php-format
+msgid "Delete %s"
+msgstr "%s löschen"
+
+#: templates/accounts/accounts.inc:43
+msgid "Delete all"
+msgstr "Alles lXschen"
+
+#: lib/UI/VarRenderer/fima.php:39
+msgid "Delete postings."
+msgstr "Buchungen löschen"
+
+#: lib/UI/VarRenderer/fima.php:65
+msgid "Delete subaccounts and postings."
+msgstr "Unterkonten und Buchungen löschen."
+
+#: lib/UI/VarRenderer/fima.php:71
+msgid "Delete subaccounts and shift postings to"
+msgstr "Unterkonten löschen und Buchungen umbuchen nach"
+
+#: account.php:92 lib/Forms/account.php:51
+msgid "Delete this account"
+msgstr "Dieses Konto löschen"
+
+#: account.php:185 account.php:202
+#, php-format
+msgid "Delete: %s"
+msgstr "Löschen: %s"
+
+#: postings.php:396
+#, php-format
+msgid "Deleted %d existing postings."
+msgstr "%d existierende Buchungen gelöscht."
+
+#: postings.php:494
+#, php-format
+msgid "Deleted %d original postings."
+msgstr "%d ursprüngliche Buchungen gelöscht."
+
+#: postings.php:305
+#, php-format
+msgid "Deleted %d postings."
+msgstr "%d Buchungen gelöscht."
+
+#: account.php:223
+#, php-format
+msgid "Deleted %s."
+msgstr "%s gelöscht."
+
+#: accounts.php:39
+msgid "Deleted all accounts and postings."
+msgstr "Alle Konten und Buchungen gelöscht."
+
+#: config/prefs.php.dist:124
+msgid "Descending"
+msgstr "Absteigend"
+
+#: templates/postings/posting_headers.inc:31
+msgid "Descriptio_n"
+msgstr "A_nmerkung"
+
+#: data.php:41 lib/Forms/account.php:46 lib/Forms/CreateLedger.php:36
+#: lib/Forms/EditLedger.php:43 config/prefs.php.dist:99
+#: config/prefs.php.dist:113
+msgid "Description"
+msgstr "Anmerkung"
+
+#: lib/Report/GeneralOverview.php:87 lib/Report/Analysis.php:89
+#: lib/Report/AccountOverview.php:87
+msgid "Diff. (%)"
+msgstr "Diff. (%)"
+
+#: report.php:97 report.php:98 report.php:99 report.php:100 report.php:101
+#: report.php:102 report.php:103 lib/Report/GeneralOverview.php:84
+#: lib/Report/Analysis.php:86 lib/Report/Analysis.php:362
+#: lib/Report/AccountOverview.php:84
+msgid "Difference"
+msgstr "Differenz"
+
+#: config/prefs.php.dist:168
+msgid "Do you want to confirm deleting postings?"
+msgstr "Möchten Sie das Löschen von Buchungen bestätigen?"
+
+#: templates/postings/transfer.inc:32
+msgid "Don't summarize."
+msgstr "Nicht summieren."
+
+#: ledgers/index.php:34 templates/ledgers_list.php:31
+#: templates/accounts/accounts.inc:44 templates/postings/actions.inc:10
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: lib/Forms/EditLedger.php:39 templates/accounts/accounts.inc:44
+#, php-format
+msgid "Edit %s"
+msgstr "%s bearbeiten"
+
+#: postings.php:147 postings.php:179
+msgid "Edit Postings"
+msgstr "Buchungen bearbeiten"
+
+#: templates/postings/header.inc:9
+msgid "Edit Search Query"
+msgstr "Suchabfrage bearbeiten"
+
+#: account.php:195 lib/Forms/account.php:86
+msgid "Edit this account"
+msgstr "Dieses Konto bearbeiten"
+
+#: account.php:82 account.php:99
+#, php-format
+msgid "Edit: %s"
+msgstr "Bearbeiten: %s"
+
+#: config/prefs.php.dist:159
+msgid "Enter expenses with negative sign?"
+msgstr "Ausgaben mit negativem Vorzeichen eingeben?"
+
+#: lib/Block/summary.php:106 lib/Block/summary.php:122
+msgid "Error when retrieving results."
+msgstr "Fehler beim Abfragen der Ergebnisse."
+
+#: lib/Fima.php:450
+msgid "Expense"
+msgstr "Ausgaben"
+
+#: templates/data/export.inc:16
+msgid "Export"
+msgstr "Exportieren"
+
+#: templates/data/export.inc:6
+msgid "Export Postings"
+msgstr "Buchungen exportieren"
+
+#: data.php:65 data.php:124 data.php:153
+#, php-format
+msgid "Failed to access the ledger: %s"
+msgstr "Fehler beim Zugriff auf das Buch: %s"
+
+#: lib/Block/summary.php:3
+msgid "Finances Results"
+msgstr "Finanzen Ergebnisse"
+
+#: postings.php:639 config/prefs.php.dist:84
+msgid "First Page"
+msgstr "Erste Seite"
+
+#: lib/Fima.php:473 config/prefs.php.dist:53
+msgid "Forecast"
+msgstr "Vorschau"
+
+#: templates/postings/javascript_edit.inc:13
+msgid "Friday"
+msgstr "Freitag"
+
+#: config/prefs.php.dist:13 config/prefs.php.dist:20
+msgid "General Options"
+msgstr "Allgemeine Einstellungen"
+
+#: config/report.php.dist:9
+msgid "General Overview"
+msgstr "Gesamtübersicht"
+
+#: templates/data/import.inc:7
+#, php-format
+msgid "Import Postings, Step %d"
+msgstr "Buchungen importieren, Schritt %d"
+
+#: data.php:213
+msgid "Import/Export Postings"
+msgstr "Buchungen importieren/exportieren"
+
+#: lib/Fima.php:449
+msgid "Income"
+msgstr "Einnahmen"
+
+#: config/prefs.php.dist:21
+msgid "Interface Options"
+msgstr "Oberflächen-Einstellungen"
+
+#: templates/postings/navbar.inc:11
+msgid "Invert"
+msgstr "Umkehren"
+
+#: lib/UI/VarRenderer/fima.php:59
+msgid "Keep subaccounts and postings."
+msgstr "Unterkonten und Buchungen behalten."
+
+#: postings.php:650 config/prefs.php.dist:85
+msgid "Last Page"
+msgstr "Letzte Seite"
+
+#: ledgers/index.php:33 templates/ledgers_list.php:16
+msgid "Ledger"
+msgstr "Buch"
+
+#: templates/ledgers_list.php:12
+msgid "Ledger List"
+msgstr "Buch-Liste"
+
+#: ledgers/index.php:40 templates/ledgers_list.php:2
+msgid "Manage Ledgers"
+msgstr "Buch-Verwaltung"
+
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Menüliste"
+
+#: templates/postings/javascript_edit.inc:9
+msgid "Monday"
+msgstr "Montag"
+
+#: accounts.php:91
+msgid "My Accounts"
+msgstr "Meine Konten"
+
+#: postings.php:39
+msgid "My Postings"
+msgstr "Meine Buchungen"
+
+#: lib/Forms/account.php:44 lib/Forms/CreateLedger.php:35
+#: lib/Forms/EditLedger.php:42
+msgid "Name"
+msgstr "Name"
+
+#: account.php:67 account.php:99
+msgid "New Account"
+msgstr "Neues Konto"
+
+#: templates/data/import.inc:31
+msgid "Next"
+msgstr "Weiter"
+
+#: postings.php:648
+msgid "Next Page"
+msgstr "Nächste Seite"
+
+#: postings.php:615
+msgid "No Postings"
+msgstr "Keine Buchungen"
+
+#: templates/reports/empty.inc:2
+msgid "No data for this report."
+msgstr "Keine Daten für diese Auswertung."
+
+#: lib/Report/GeneralOverview.php:45 lib/Report/Trend.php:50
+#: lib/Report/PeriodOverview.php:44 lib/Report/Analysis.php:51
+#: lib/Report/AccountOverview.php:44 lib/Report/AssetOverview.php:48
+msgid "No display type"
+msgstr "Kein Anzeigetyp"
+
+#: lib/Report.php:170 lib/ReportGraph.php:175
+msgid "No report driver loaded"
+msgstr "Kein Auswertungstreiber"
+
+#: lib/Block/summary.php:154
+msgid "No results to display"
+msgstr "Keine Ergebnisse"
+
+#: templates/prefs/closedperiodselect.inc:4 templates/postings/navbar.inc:10
+msgid "None"
+msgstr "Keine"
+
+#: templates/postings/navbar.inc:15
+#, php-format
+msgid "Not %s"
+msgstr "Nicht %s"
+
+#: lib/Driver/sql.php:133 lib/Driver/sql.php:169 lib/Driver/sql.php:326
+msgid "Not found"
+msgstr "Nicht gefunden"
+
+#: lib/Forms/account.php:42
+msgid "Number"
+msgstr "Nummer"
+
+#: lib/Block/summary.php:44
+msgid "Number of months to display"
+msgstr "Anzahl der anzuzeigenden Monate"
+
+#: templates/menu.inc:19
+msgid "Open Ledger"
+msgstr "Buch Ã¶ffnen"
+
+#: templates/reports/reports.inc:104
+msgid "Open Report"
+msgstr "Auswertung durchführen"
+
+#: templates/reports/reports.inc:75
+msgid "Other"
+msgstr "Andere"
+
+#: templates/reports/reports.inc:55
+msgid "P_eriod"
+msgstr "Z_eitraum"
+
+#: templates/postings/header.inc:18
+#, php-format
+msgid "Page %d of %d"
+msgstr "Seite %d von %d"
+
+#: lib/Report/PeriodOverview.php:67 lib/Report/AccountOverview.php:73
+msgid "Period"
+msgstr "Zeitraum"
+
+#: config/report.php.dist:10
+msgid "Period Overview"
+msgstr "Periodenübersicht"
+
+#: templates/postings/transfer.inc:11
+msgid "Period from"
+msgstr "Zeitraum von"
+
+#: templates/postings/transfer.inc:50
+msgid "Period to"
+msgstr "Zeitraum bis"
+
+#: lib/Forms/DeleteLedger.php:55
+msgid "Permission denied"
+msgstr "Zugriff verweigert"
+
+#: templates/postings/javascript_transfer.inc:6
+msgid "Please select another posting type where to transfer postings to."
+msgstr ""
+"Bitte wählen Sie einen anderen Datenkreis in welchen den Buchungen "
+"transferiert werden sollen."
+
+#: templates/postings/posting_headers.inc:26 templates/postings/shift.inc:29
+msgid "Posting A_ccount"
+msgstr "Bewe_gungskonto"
+
+#: config/prefs.php.dist:97 config/prefs.php.dist:111
+msgid "Posting Account"
+msgstr "Bewegungskonto"
+
+#: templates/postings/shift.inc:13
+msgid "Posting T_ype"
+msgstr "Daten_kreis"
+
+#: lib/Forms/account.php:81
+msgid "Postings"
+msgstr "Buchungen"
+
+#: config/prefs.php.dist:75
+msgid "Postings per page in the list view."
+msgstr "Anzahl der Buchungen pro Seite."
+
+#: data.php:164
+msgid "Postings successfully purged."
+msgstr "Buchungen erfolgreich gelöscht."
+
+#: postings.php:641
+msgid "Previous Page"
+msgstr "Vorherige Seite"
+
+#: templates/reports/reports.inc:65
+msgid "Re_ference"
+msgstr "Re_ferenz"
+
+#: lib/Forms/DeleteLedger.php:42
+#, php-format
+msgid ""
+"Really delete the ledger \"%s\"? This cannot be undone and all data on this "
+"ledger will be permanently removed."
+msgstr ""
+"Das Buch \"%s\" wirklich löschen? Dieser Vorgang kann nicht rückgängig "
+"gemacht werden, und alle Daten in diesem Buch werden endgültig gelöscht."
+
+#: report.php:96 report.php:99 report.php:101 report.php:102 report.php:103
+#: lib/Report/GeneralOverview.php:81 lib/Report/Analysis.php:83
+#: lib/Report/AccountOverview.php:81
+msgid "Reference"
+msgstr "Referenz"
+
+#: templates/postings/header.inc:5
+msgid "Refresh List"
+msgstr "Liste aktualisieren"
+
+#: templates/data/import.inc:18
+msgid ""
+"Replace existing postings with the imported one? <strong>Warning: This "
+"deletes all existing postings.</strong>"
+msgstr ""
+"Existierenden Buchungen durch die importierten ersetzen? <strong>Achtung: "
+"Dies löscht alle existierenden Buchungen.</strong>"
+
+#: templates/reports/reports.inc:12
+msgid "Repor_t"
+msgstr "Auswer_tung"
+
+#: lib/ReportGraph.php:86 templates/reports/graph.inc:2
+msgid "Report"
+msgstr "Auswertung"
+
+#: report.php:152
+#, php-format
+msgid "Report %s"
+msgstr "Auswertung %s"
+
+#: report.php:111 lib/Block/tree_menu.php:21
+msgid "Reports"
+msgstr "Auswertungen"
+
+#: templates/reports/reports.inc:105
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: lib/Report/Analysis.php:361
+msgid "Rest"
+msgstr "Rest"
+
+#: lib/Report/GeneralOverview.php:61 lib/Report/PeriodOverview.php:76
+#: templates/postings/edit.inc:47
+msgid "Result"
+msgstr "Ergebnis"
+
+#: config/prefs.php.dist:135
+msgid "SQL (% and _)"
+msgstr "SQL (% und _)"
+
+#: templates/postings/actions.inc:11
+msgid "S_hift"
+msgstr "Umbuc_hen"
+
+#: templates/postings/javascript_edit.inc:14
+msgid "Saturday"
+msgstr "Samstag"
+
+#: lib/Forms/account.php:49 lib/Forms/EditLedger.php:45
+#: templates/postings/actions.inc:15
+msgid "Save"
+msgstr "Speichern"
+
+#: account.php:162 lib/Forms/account.php:49
+msgid "Save and New"
+msgstr "Speichern und Neu"
+
+#: postings.php:267
+#, php-format
+msgid "Saved %d postings."
+msgstr "%d Buchungen gespeichert."
+
+#: account.php:160
+#, php-format
+msgid "Saved %s."
+msgstr "%s gespeichert."
+
+#: lib/Fima.php:760 lib/Block/tree_menu.php:19 templates/search/search.inc:106
+#: templates/accounts/accounts.inc:47 templates/postings/header.inc:7
+msgid "Search"
+msgstr "Suche"
+
+#: templates/search/search.inc:43
+msgid "Search A_sset Accounts"
+msgstr "Vermögen_skonten suchen"
+
+#: templates/search/search.inc:77
+msgid "Search Amo_unt Start"
+msgstr "Anfangsbetrag suchen"
+
+#: templates/search/search.inc:85
+msgid "Search Amoun_t End"
+msgstr "Endbetrag suchen"
+
+#: templates/search/search.inc:35
+msgid "Search Dat_e End"
+msgstr "Enddatum suchen"
+
+#: templates/search/search.inc:69
+msgid "Search Descriptio_n"
+msgstr "A_nmerkung suchen"
+
+#: templates/search/search.inc:51
+msgid "Search Posting A_ccounts"
+msgstr "Bewe_gungskonten suchen"
+
+#: templates/search/search.inc:15
+msgid "Search Posting T_ype"
+msgstr "_Datenkreis suchen"
+
+#: search.php:71 templates/postings/header.inc:7
+msgid "Search Postings"
+msgstr "Buchungen suchen"
+
+#: postings.php:519
+msgid "Search Results"
+msgstr "Ergebnisse suchen"
+
+#: templates/search/search.inc:27
+msgid "Search _Date Start"
+msgstr "Anfangs_datum suchen"
+
+#: templates/search/search.inc:93
+msgid "Search e.o. Postin_gs"
+msgstr "a.o. Buchun_gen suchen"
+
+#: templates/postings/navbar.inc:8
+msgid "Select"
+msgstr "Auswählen"
+
+#: lib/Forms/account.php:146
+msgid "Select another account where to shift postings to."
+msgstr "Wählen Sie ein anderes Konto zum Umbuchen der Buchungen."
+
+#: lib/Forms/account.php:181
+msgid "Select another account where to shift subaccount postings to."
+msgstr ""
+"Wählen Sie ein anderes Konto zum Umbuchen der Buchungen der Unterkonten."
+
+#: config/prefs.php.dist:181
+msgid "Select the canvas size for chart reports:"
+msgstr "Wählen Sie die Grafikgröße für Auswertungsdiagramme:"
+
+#: templates/data/import.inc:21
+msgid "Select the charset of the source file:"
+msgstr "Wählen Sie den Zeichensatz der importierten Datei:"
+
+#: templates/data/export.inc:10
+msgid "Select the export format:"
+msgstr "Wählen Sie das Exportformat:"
+
+#: templates/data/import.inc:29
+msgid "Select the file to import:"
+msgstr "Wählen Sie die Datei, die importiert werden soll:"
+
+#: config/prefs.php.dist:150
+msgid "Select the format for amounts:"
+msgstr "Wählen Sie das Format für Beträge:"
+
+#: config/prefs.php.dist:137
+msgid "Select the format for wildcards for text search:"
+msgstr "Wählen Sie das Format für Platzhalter in der Textsuche:"
+
+#: templates/data/import.inc:11
+msgid "Select the format of the source file:"
+msgstr "Wählen Sie das Format der importierten Datei:"
+
+#: lib/Fima.php:432
+msgid "Set date"
+msgstr "Datum setzen"
+
+#: templates/postings/actions.inc:11
+msgid "Shift"
+msgstr "Umbuchen"
+
+#: postings.php:157
+msgid "Shift Postings"
+msgstr "Buchungen umbuchen"
+
+#: lib/UI/VarRenderer/fima.php:45
+msgid "Shift postings to"
+msgstr "Buchungen umbuchen auf"
+
+#: postings.php:342
+#, php-format
+msgid "Shifted %d postings."
+msgstr "%d Buchungen umgebucht."
+
+#: templates/postings/shift.inc:3
+msgid "Shifting"
+msgstr "Umbuchen"
+
+#: lib/Block/summary.php:39
+msgid "Show summary of this ledger"
+msgstr "Buchübersicht anzeigen"
+
+#: templates/reports/table.inc:23
+#, php-format
+msgid "Sort by %s"
+msgstr "Sortieren nach %s"
+
+#: templates/postings/posting_headers.inc:35
+msgid "Sort by Amount"
+msgstr "Sortieren nach Betrag"
+
+#: templates/postings/posting_headers.inc:20
+msgid "Sort by Asset Account"
+msgstr "Sortieren nach Vermögenskonto"
+
+#: templates/postings/posting_headers.inc:15
+msgid "Sort by Date"
+msgstr "Sortieren nach Datum"
+
+#: templates/postings/posting_headers.inc:30
+msgid "Sort by Description"
+msgstr "Sortieren nach Anmerkung"
+
+#: templates/postings/posting_headers.inc:25
+msgid "Sort by Posting Account"
+msgstr "Sortieren nach Bewegungskonto"
+
+#: config/prefs.php.dist:125
+msgid "Sort direction:"
+msgstr "Sortierrichtung:"
+
+#: config/prefs.php.dist:100
+msgid "Sort postings by:"
+msgstr "Buchungen sortieren nach:"
+
+#: lib/Forms/account.php:82
+msgid "Subaccounts"
+msgstr "Unterkonten"
+
+#: templates/postings/transfer.inc:34
+msgid "Summarize by combining."
+msgstr "Summieren durch Kombination."
+
+#: templates/postings/transfer.inc:36
+msgid "Summarize by posting against"
+msgstr "Summieren durch Buchen gegen"
+
+#: postings.php:429 postings.php:443 postings.php:454
+msgid "Summarized"
+msgstr "Summiert"
+
+#: postings.php:478
+#, php-format
+msgid "Summarized %d postings."
+msgstr "%d Buchungen summiert."
+
+#: templates/postings/javascript_edit.inc:8
+msgid "Sunday"
+msgstr "Sonntag"
+
+#: data.php:26
+msgid "TSV"
+msgstr "TSV"
+
+#: templates/data/export.inc:13 templates/data/import.inc:14
+msgid "Tab separated values"
+msgstr "Tabulator getrennte Werte"
+
+#: data.php:201
+#, php-format
+msgid "The %s file didn't contain any postings."
+msgstr "Die %s-Datei enthielt keine Buchungen."
+
+#: lib/Driver.php:72
+msgid "The Finances backend is not currently available."
+msgstr "Der Finanzserver ist zur Zeit nicht verfügbar."
+
+#: lib/Driver.php:364
+#, php-format
+msgid "The Finances backend is not currently available: %s"
+msgstr "Der Finanzserver ist zur Zeit nicht verfügbar: %s"
+
+#: lib/ReportGraph.php:62
+msgid "The Finances report graphs are not currently available."
+msgstr "Die grafischen Finanzauswertungen sind zur Zeit nicht verfügbar."
+
+#: lib/ReportGraph.php:185
+#, php-format
+msgid "The Finances report graphs are not currently available: %s"
+msgstr "Die grafischen Finanzauswertungen sind zur Zeit nicht verfügbar: %s"
+
+#: lib/Report.php:48
+msgid "The Finances reports are not currently available."
+msgstr "Die Finanzauswertungen sind zur Zeit nicht verfügbar."
+
+#: lib/Report.php:180
+#, php-format
+msgid "The Finances reports are not currently available: %s"
+msgstr "Die Finanzauswertungen sind zur Zeit nicht verfügbar: %s"
+
+#: account.php:121
+#, php-format
+msgid "The account including all postings was shifted from number %s to %s."
+msgstr ""
+"Das Konto wurde inklusive aller Buchungen von Nummer %s nach %s verlagert."
+
+#: account.php:118
+#, php-format
+msgid "The account number %s is already used by the account %s."
+msgstr "Die Kontonummer %s wird bereits verwendet vom Konto %s."
+
+#: account.php:131
+#, php-format
+msgid "The account type was set to %s."
+msgstr "Der Kontotyp wurde auf %s geändert."
+
+#: report.php:147
+msgid "The graphs library could not be loaded."
+msgstr "Die Grafikbibliothek konnte nicht geladen werden."
+
+#: ledgers/create.php:31
+#, php-format
+msgid "The ledger \"%s\" has been created."
+msgstr "Das Buch \"%s\" wurde erstellt."
+
+#: ledgers/delete.php:48
+#, php-format
+msgid "The ledger \"%s\" has been deleted."
+msgstr "Das Buch \"%s\" wurde gelöscht."
+
+#: ledgers/edit.php:42
+#, php-format
+msgid "The ledger \"%s\" has been renamed to \"%s\"."
+msgstr "Das Buch \"%s\" wurde in \"%s\" umbenannt."
+
+#: ledgers/edit.php:44
+#, php-format
+msgid "The ledger \"%s\" has been saved."
+msgstr "Das Buch \"%s\" wurde gespeichert."
+
+#: data.php:162
+#, php-format
+msgid "The postings could not be purged: %s"
+msgstr "Die Buchungen konnte nicht gelöscht werden: %s"
+
+#: config/prefs.php.dist:114
+msgid "Then:"
+msgstr "Dann:"
+
+#: templates/postings/empty.inc:3
+msgid "There are no appropriate postings."
+msgstr "Es existieren keine entsprechenden Buchungen."
+
+#: lib/Report.php:128
+#, php-format
+msgid "There was a problem creating the report graph: %s."
+msgstr "Beim Erstellen der Auswertungsgrafik ist ein Problem aufgetreten: %s"
+
+#: report.php:129
+#, php-format
+msgid "There was a problem creating the report: %s."
+msgstr "Beim Erstellen der Auswertung ist ein Problem aufgetreten: %s"
+
+#: accounts.php:36
+#, php-format
+msgid "There was a problem deleting all accounts and postings: %s"
+msgstr ""
+"Beim Löschen aller Konten und Buchungen ist ein Problem aufgetreten: %s"
+
+#: postings.php:389
+#, php-format
+msgid "There was a problem deleting an existing posting: %s"
+msgstr ""
+"Beim Löschen einer existierenden Buchung ist ein Problem aufgetreten: %s"
+
+#: postings.php:487
+#, php-format
+msgid "There was a problem deleting an original posting: %s"
+msgstr ""
+"Beim Löschen einer ursprünglichen Buchung ist ein Problem aufgetreten: %s"
+
+#: postings.php:298
+#, php-format
+msgid "There was a problem deleting posting #%d: %s"
+msgstr "Beim Löschen der Buchung #%d ist ein Probelm aufgetreten: %s"
+
+#: account.php:221
+#, php-format
+msgid "There was a problem deleting the account: %s."
+msgstr "Beim Löschen des Kontos ist ein Problem aufgetreten: %s."
+
+#: lib/Report.php:135
+#, php-format
+msgid "There was a problem executing the report graph: %s."
+msgstr "Beim Ausführen der Auswertungsgrafik ist ein Problem aufgetreten: %s."
+
+#: report.php:139
+#, php-format
+msgid "There was a problem executing the report: %s."
+msgstr "Beim Ausführen der Auswertung ist ein Problem aufgetreten: %s."
+
+#: account.php:158
+#, php-format
+msgid "There was a problem saving the account: %s."
+msgstr "Beim Speichern des Kontos ist ein Problem aufgetreten: %s."
+
+#: postings.php:244 postings.php:472
+#, php-format
+msgid "There was a problem saving the posting: %s."
+msgstr "Beim Speichern der Buchung ist ein Problem aufgetreten: %s."
+
+#: postings.php:335
+#, php-format
+msgid "There was a problem shifting posting #%d: %s"
+msgstr "Beim Umbuchen der Buchung #%d ist ein Problem aufgetreten: %s"
+
+#: data.php:204
+#, php-format
+msgid "There was an error importing the data: %s"
+msgstr "Beim Importieren der Daten ist ein Problem aufgetreten: %s"
+
+#: data.php:103
+msgid "There were no postings to export."
+msgstr "Es konnten keine Buchungen zum Exportieren gefunden werden."
+
+#: data.php:137
+msgid "This file format is not supported."
+msgstr "Dieses Dateiformat wird nicht unterstützt."
+
+#: ledgers/delete.php:24
+msgid "This ledger cannot be deleted."
+msgstr "Dieses Buch kann nicht gelöscht werden."
+
+#: templates/postings/javascript_edit.inc:12
+msgid "Thursday"
+msgstr "Donnerstag"
+
+#: lib/Report/Trend.php:79 lib/Report/Trend.php:156
+#: lib/Report/PeriodOverview.php:90 lib/Report/Analysis.php:201
+#: lib/Report/AccountOverview.php:101 lib/Report/AssetOverview.php:76
+#: lib/Block/summary.php:76 templates/postings/list.inc:13
+msgid "Total Result"
+msgstr "Gesamtergebnis"
+
+#: templates/postings/actions.inc:12
+msgid "Trans_fer"
+msgstr "Trans_fer"
+
+#: templates/postings/actions.inc:12
+msgid "Transfer"
+msgstr "Transfer"
+
+#: postings.php:165
+msgid "Transfer Postings"
+msgstr "Buchungen transferieren"
+
+#: templates/postings/transfer.inc:3
+msgid "Transfer from"
+msgstr "Transfer von"
+
+#: templates/postings/transfer.inc:42
+msgid "Transfer to"
+msgstr "Transfer nach"
+
+#: postings.php:478
+#, php-format
+msgid "Transfered %d postings."
+msgstr "%d Buchungen transferiert."
+
+#: config/report.php.dist:14
+msgid "Trend"
+msgstr "Trend"
+
+#: templates/postings/javascript_edit.inc:10
+msgid "Tuesday"
+msgstr "Dienstag"
+
+#: lib/Report/GeneralOverview.php:73 lib/Forms/account.php:43
+msgid "Type"
+msgstr "Typ"
+
+#: lib/Forms/DeleteLedger.php:62
+#, php-format
+msgid "Unable to delete \"%s\": %s"
+msgstr "\"%s\" kann nicht gelöscht werden: %s"
+
+#: lib/Report.php:183 lib/ReportGraph.php:188 lib/Driver.php:367
+#, php-format
+msgid "Unable to load the definition of %s."
+msgstr "Der %s-Treiber konnte nicht geladen werden."
+
+#: lib/Forms/EditLedger.php:54
+#, php-format
+msgid "Unable to save ledger \"%s\": %s"
+msgstr "Das Buch \"%s\" konnte nicht gespeichert werden: %s"
+
+#: postings.php:581 postings.php:590
+msgid "Unknown"
+msgstr "Unbekannt"
+
+#: templates/accounts/accounts.inc:47
+#, php-format
+msgid "View %s"
+msgstr "%s anzeigen"
+
+#: templates/postings/javascript_edit.inc:11
+msgid "Wednesday"
+msgstr "Mittwoch"
+
+#: config/prefs.php.dist:86
+msgid "When displaying the postings, which page do you want to start on?"
+msgstr "Bei der Anzeige von Buchungen, bei welcher Seite möchten Sie beginnen?"
+
+#: ledgers/edit.php:28
+msgid "You are not allowed to change this ledger."
+msgstr "Sie dürfen dieses Buch nicht Ã¤ndern."
+
+#: ledgers/delete.php:35
+msgid "You are not allowed to delete this ledger."
+msgstr "Sie dürfen dieses Buch nicht löschen."
+
+#: templates/postings/javascript_list.inc:39
+msgid "You must select at least one posting first."
+msgstr "Sie müssen zuerst mindestens eine Buchung auswählen."
+
+#: templates/prefs/ledgerselect.inc:10
+msgid "Your active ledger:"
+msgstr "Ihr aktives Buch:"
+
+#: config/prefs.php.dist:55
+msgid "Your active posting type:"
+msgstr "Ihr aktiver Datenkreis:"
+
+#: lib/Fima.php:761
+msgid "_Accounts"
+msgstr "_Konten"
+
+#: templates/reports/reports.inc:97
+msgid "_Chart"
+msgstr "_Diagramm"
+
+#: templates/postings/actions.inc:9
+msgid "_Delete"
+msgstr "_Löschen"
+
+#: templates/postings/transfer.inc:58
+msgid "_Delete existing Postings"
+msgstr "Existierende Buchungen _löschen"
+
+#: templates/reports/reports.inc:24
+msgid "_Display"
+msgstr "An_zeige"
+
+#: templates/postings/actions.inc:10
+msgid "_Edit"
+msgstr "_Bearbeiten"
+
+#: lib/Fima.php:771
+msgid "_Import/Export"
+msgstr "_Import/Export"
+
+#: templates/postings/transfer.inc:19
+msgid "_Keep original Postings"
+msgstr "Ursprüngliche Buchungen be_halten"
+
+#: lib/Fima.php:758
+msgid "_List Postings"
+msgstr "_Buchungen"
+
+#: lib/Fima.php:764
+msgid "_My Ledgers"
+msgstr "_Meine Bücher"
+
+#: templates/reports/reports.inc:88
+msgid "_Null Rows"
+msgstr "_Leerzeilen"
+
+#: lib/Fima.php:775
+msgid "_Print"
+msgstr "_Drucken"
+
+#: lib/Fima.php:768
+msgid "_Reports"
+msgstr "_Auswertungen"
+
+#: templates/reports/reports.inc:84
+msgid "_Subaccounts"
+msgstr "_Unterkonten"
+
+#: templates/postings/transfer.inc:28
+msgid "_Summarize Accounts"
+msgstr "Konten _summieren"
+
+#: templates/reports/reports.inc:92
+msgid "_Yearly"
+msgstr "_Jährlich"
+
+#: templates/postings/shift.inc:16 templates/postings/shift.inc:24
+#: templates/postings/shift.inc:32
+msgid "don't change"
+msgstr "nicht Ã¤ndern"
+
+#: data.php:43 lib/Forms/account.php:45
+#: templates/postings/posting_headers.inc:43
+msgid "e.o."
+msgstr "a.o."
+
+#: postings.php:587
+#, php-format
+msgid "e.o. %s"
+msgstr "a.o. %s"
+
+#: search.php:64
+msgid "e.o. postings only"
+msgstr "nur a.o. Buchungen"
+
+#: search.php:65
+msgid "no e.o. postings"
+msgstr "keine a.o. Buchungen"
+
+#: config/prefs.php.dist:136
+msgid "none"
+msgstr "Keine"
+
+#: data.php:111 templates/data/export.inc:1
+msgid "postings.csv"
+msgstr "buchungen.csv"
+
+#: data.php:116
+msgid "postings.tsv"
+msgstr "buchungen.tsv"
diff --git a/fima/po/fima.pot b/fima/po/fima.pot
new file mode 100644 (file)
index 0000000..2624d0a
--- /dev/null
@@ -0,0 +1,1314 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Horde Project
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+"POT-Creation-Date: 2009-03-23 07:41+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: report.php:97 report.php:98 report.php:99 report.php:100 report.php:101
+#: report.php:102 report.php:103
+msgid "%"
+msgstr ""
+
+#: postings.php:270
+#, php-format
+msgid "%d postings not saved."
+msgstr ""
+
+#: templates/postings/shift.inc:5
+#, php-format
+msgid "%d selected Postings"
+msgstr ""
+
+#: postings.php:587
+#, php-format
+msgid "%s"
+msgstr ""
+
+#: lib/Report/Trend.php:113 lib/Report/Analysis.php:125
+#, php-format
+msgid "%s Result"
+msgstr ""
+
+#: report.php:95
+#, php-format
+msgid "%s [%s - %s - %s]"
+msgstr ""
+
+#: data.php:207
+#, php-format
+msgid "%s successfully imported"
+msgstr ""
+
+#: postings.php:617
+#, php-format
+msgid "%s to %s of %s Postings"
+msgstr ""
+
+#: lib/Fima.php:728 lib/Forms/DeleteLedger.php:85
+#, php-format
+msgid "%s's Ledger"
+msgstr ""
+
+#: config/prefs.php.dist:148
+msgid "-12 345 678,90"
+msgstr ""
+
+#: config/prefs.php.dist:149
+msgid "-12'345'678.90"
+msgstr ""
+
+#: config/prefs.php.dist:147
+msgid "-12,345,678.90"
+msgstr ""
+
+#: config/prefs.php.dist:146
+msgid "-12.345.678,90"
+msgstr ""
+
+#: config/prefs.php.dist:179
+msgid "1024 x 768 Pixel"
+msgstr ""
+
+#: config/prefs.php.dist:180
+msgid "1600 x 1200 Pixel"
+msgstr ""
+
+#: config/prefs.php.dist:177
+msgid "400 x 300 Pixel"
+msgstr ""
+
+#: config/prefs.php.dist:178
+msgid "800 x 600 Pixel"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:21 templates/postings/shift.inc:21
+msgid "A_sset Account"
+msgstr ""
+
+#: account.php:209
+#, php-format
+msgid "Access denied deleting account from %s."
+msgstr ""
+
+#: account.php:175
+msgid "Access denied deleting account."
+msgstr ""
+
+#: accounts.php:29
+msgid "Access denied deleting all accounts and postings."
+msgstr ""
+
+#: postings.php:289
+#, php-format
+msgid "Access denied deleting postings from %s."
+msgstr ""
+
+#: account.php:73
+msgid "Access denied editing account."
+msgstr ""
+
+#: account.php:27
+#, php-format
+msgid "Access denied on account: %s"
+msgstr ""
+
+#: accounts.php:21
+#, php-format
+msgid "Access denied on accounts: %s"
+msgstr ""
+
+#: account.php:106
+#, php-format
+msgid "Access denied saving account to %s."
+msgstr ""
+
+#: postings.php:174
+#, php-format
+msgid "Access denied saving postings to %s."
+msgstr ""
+
+#: postings.php:320
+#, php-format
+msgid "Access denied shifting postings in %s."
+msgstr ""
+
+#: postings.php:355 postings.php:359
+#, php-format
+msgid "Access denied transfering postings in %s."
+msgstr ""
+
+#: data.php:40 lib/Report/Trend.php:71 lib/Report/Analysis.php:75
+msgid "Account"
+msgstr ""
+
+#: config/report.php.dist:11
+msgid "Account Overview"
+msgstr ""
+
+#: account.php:77 account.php:179
+msgid "Account not found."
+msgstr ""
+
+#: lib/Block/tree_menu.php:20 templates/reports/reports.inc:34
+msgid "Accounts"
+msgstr ""
+
+#: config/prefs.php.dist:14
+msgid "Active Configuration"
+msgstr ""
+
+#: lib/Fima.php:472 config/prefs.php.dist:52
+msgid "Actual"
+msgstr ""
+
+#: postings.php:137 lib/Block/tree_menu.php:18
+msgid "Add Postings"
+msgstr ""
+
+#: lib/Fima.php:759
+msgid "Add _Postings"
+msgstr ""
+
+#: templates/postings/transfer.inc:14 templates/postings/transfer.inc:53
+#: templates/postings/navbar.inc:9
+msgid "All"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:36
+msgid "Amo_unt"
+msgstr ""
+
+#: data.php:42 config/prefs.php.dist:98 config/prefs.php.dist:112
+msgid "Amount"
+msgstr ""
+
+#: config/report.php.dist:13
+msgid "Analysis"
+msgstr ""
+
+#: templates/accounts/accounts.inc:12
+msgid ""
+"Are you sure you wish to PERMANENTLY delete all accounts and all postings?"
+msgstr ""
+
+#: templates/postings/javascript_list.inc:45
+msgid "Are you sure you wish to PERMANENTLY delete these postings?"
+msgstr ""
+
+#: config/prefs.php.dist:123
+msgid "Ascending"
+msgstr ""
+
+#: lib/Fima.php:448 lib/Report/AssetOverview.php:68
+msgid "Asset"
+msgstr ""
+
+#: data.php:39 config/prefs.php.dist:96 config/prefs.php.dist:110
+msgid "Asset Account"
+msgstr ""
+
+#: config/report.php.dist:12
+msgid "Asset Overview"
+msgstr ""
+
+#: lib/Report/GeneralOverview.php:63 lib/Report/GeneralOverview.php:98
+#: lib/Report/PeriodOverview.php:78 lib/Report/AssetOverview.php:92
+#: lib/Block/summary.php:77
+msgid "Asset Result"
+msgstr ""
+
+#: templates/postings/actions.inc:17
+msgid "Autofill"
+msgstr ""
+
+#: lib/Block/summary.php:35
+msgid "Block title"
+msgstr ""
+
+#: lib/Fima.php:474 config/prefs.php.dist:54
+msgid "Budget"
+msgstr ""
+
+#: data.php:25
+msgid "CSV"
+msgstr ""
+
+#: templates/reports/reports.inc:80
+msgid "C_umulate"
+msgstr ""
+
+#: lib/Forms/DeleteLedger.php:44 lib/Forms/DeleteLedger.php:50
+msgid "Cancel"
+msgstr ""
+
+#: ledgers/index.php:35 templates/ledgers_list.php:33
+msgid "Change Permissions"
+msgstr ""
+
+#: config/prefs.php.dist:22
+msgid "Change the display and input options."
+msgstr ""
+
+#: templates/postings/posting_headers.inc:60
+msgid "Check _All/None"
+msgstr ""
+
+#: config/prefs.php.dist:15
+msgid "Choose your active Ledger and Posting Type."
+msgstr ""
+
+#: templates/search/search.inc:107
+msgid "Clear Form"
+msgstr ""
+
+#: templates/search/search.inc:8 templates/postings/header.inc:10
+msgid "Clear Search Query"
+msgstr ""
+
+#: lib/Forms/account.php:47
+msgid "Closed"
+msgstr ""
+
+#: templates/prefs/closedperiodselect.inc:3
+msgid "Closed by period:"
+msgstr ""
+
+#: templates/data/export.inc:12 templates/data/import.inc:13
+msgid "Comma separated values"
+msgstr ""
+
+#: lib/Forms/CreateLedger.php:38
+msgid "Create"
+msgstr ""
+
+#: lib/Forms/CreateLedger.php:33
+msgid "Create Ledger"
+msgstr ""
+
+#: templates/accounts/accounts.inc:46
+msgid "Create a New Account"
+msgstr ""
+
+#: templates/ledgers_list.php:8
+msgid "Create a new Ledger"
+msgstr ""
+
+#: templates/postings/edit.inc:52
+msgid "Current Result"
+msgstr ""
+
+#: config/prefs.php.dist:134
+msgid "DOS (* and ?)"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:16
+msgid "Da_te"
+msgstr ""
+
+#: data.php:38 config/prefs.php.dist:95 config/prefs.php.dist:109
+msgid "Date"
+msgstr ""
+
+#: lib/Forms/account.php:84 lib/Forms/DeleteLedger.php:44 ledgers/index.php:36
+#: templates/ledgers_list.php:35 templates/accounts/accounts.inc:43
+#: templates/accounts/accounts.inc:45 templates/postings/actions.inc:9
+msgid "Delete"
+msgstr ""
+
+#: lib/Forms/DeleteLedger.php:39 templates/accounts/accounts.inc:45
+#, php-format
+msgid "Delete %s"
+msgstr ""
+
+#: templates/accounts/accounts.inc:43
+msgid "Delete all"
+msgstr ""
+
+#: lib/UI/VarRenderer/fima.php:39
+msgid "Delete postings."
+msgstr ""
+
+#: lib/UI/VarRenderer/fima.php:65
+msgid "Delete subaccounts and postings."
+msgstr ""
+
+#: lib/UI/VarRenderer/fima.php:71
+msgid "Delete subaccounts and shift postings to"
+msgstr ""
+
+#: account.php:92 lib/Forms/account.php:51
+msgid "Delete this account"
+msgstr ""
+
+#: account.php:185 account.php:202
+#, php-format
+msgid "Delete: %s"
+msgstr ""
+
+#: postings.php:396
+#, php-format
+msgid "Deleted %d existing postings."
+msgstr ""
+
+#: postings.php:494
+#, php-format
+msgid "Deleted %d original postings."
+msgstr ""
+
+#: postings.php:305
+#, php-format
+msgid "Deleted %d postings."
+msgstr ""
+
+#: account.php:223
+#, php-format
+msgid "Deleted %s."
+msgstr ""
+
+#: accounts.php:39
+msgid "Deleted all accounts and postings."
+msgstr ""
+
+#: config/prefs.php.dist:124
+msgid "Descending"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:31
+msgid "Descriptio_n"
+msgstr ""
+
+#: data.php:41 lib/Forms/account.php:46 lib/Forms/CreateLedger.php:36
+#: lib/Forms/EditLedger.php:43 config/prefs.php.dist:99
+#: config/prefs.php.dist:113
+msgid "Description"
+msgstr ""
+
+#: lib/Report/GeneralOverview.php:87 lib/Report/Analysis.php:89
+#: lib/Report/AccountOverview.php:87
+msgid "Diff. (%)"
+msgstr ""
+
+#: report.php:97 report.php:98 report.php:99 report.php:100 report.php:101
+#: report.php:102 report.php:103 lib/Report/GeneralOverview.php:84
+#: lib/Report/Analysis.php:86 lib/Report/Analysis.php:362
+#: lib/Report/AccountOverview.php:84
+msgid "Difference"
+msgstr ""
+
+#: config/prefs.php.dist:168
+msgid "Do you want to confirm deleting postings?"
+msgstr ""
+
+#: templates/postings/transfer.inc:32
+msgid "Don't summarize."
+msgstr ""
+
+#: ledgers/index.php:34 templates/ledgers_list.php:31
+#: templates/accounts/accounts.inc:44 templates/postings/actions.inc:10
+msgid "Edit"
+msgstr ""
+
+#: lib/Forms/EditLedger.php:39 templates/accounts/accounts.inc:44
+#, php-format
+msgid "Edit %s"
+msgstr ""
+
+#: postings.php:147 postings.php:179
+msgid "Edit Postings"
+msgstr ""
+
+#: templates/postings/header.inc:9
+msgid "Edit Search Query"
+msgstr ""
+
+#: account.php:195 lib/Forms/account.php:86
+msgid "Edit this account"
+msgstr ""
+
+#: account.php:82 account.php:99
+#, php-format
+msgid "Edit: %s"
+msgstr ""
+
+#: config/prefs.php.dist:159
+msgid "Enter expenses with negative sign?"
+msgstr ""
+
+#: lib/Block/summary.php:106 lib/Block/summary.php:122
+msgid "Error when retrieving results."
+msgstr ""
+
+#: lib/Fima.php:450
+msgid "Expense"
+msgstr ""
+
+#: templates/data/export.inc:16
+msgid "Export"
+msgstr ""
+
+#: templates/data/export.inc:6
+msgid "Export Postings"
+msgstr ""
+
+#: data.php:65 data.php:124 data.php:153
+#, php-format
+msgid "Failed to access the ledger: %s"
+msgstr ""
+
+#: lib/Block/summary.php:3
+msgid "Finances Results"
+msgstr ""
+
+#: postings.php:639 config/prefs.php.dist:84
+msgid "First Page"
+msgstr ""
+
+#: lib/Fima.php:473 config/prefs.php.dist:53
+msgid "Forecast"
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:13
+msgid "Friday"
+msgstr ""
+
+#: config/prefs.php.dist:13 config/prefs.php.dist:20
+msgid "General Options"
+msgstr ""
+
+#: config/report.php.dist:9
+msgid "General Overview"
+msgstr ""
+
+#: templates/data/import.inc:7
+#, php-format
+msgid "Import Postings, Step %d"
+msgstr ""
+
+#: data.php:213
+msgid "Import/Export Postings"
+msgstr ""
+
+#: lib/Fima.php:449
+msgid "Income"
+msgstr ""
+
+#: config/prefs.php.dist:21
+msgid "Interface Options"
+msgstr ""
+
+#: templates/postings/navbar.inc:11
+msgid "Invert"
+msgstr ""
+
+#: lib/UI/VarRenderer/fima.php:59
+msgid "Keep subaccounts and postings."
+msgstr ""
+
+#: postings.php:650 config/prefs.php.dist:85
+msgid "Last Page"
+msgstr ""
+
+#: ledgers/index.php:33 templates/ledgers_list.php:16
+msgid "Ledger"
+msgstr ""
+
+#: templates/ledgers_list.php:12
+msgid "Ledger List"
+msgstr ""
+
+#: ledgers/index.php:40 templates/ledgers_list.php:2
+msgid "Manage Ledgers"
+msgstr ""
+
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:9
+msgid "Monday"
+msgstr ""
+
+#: accounts.php:91
+msgid "My Accounts"
+msgstr ""
+
+#: postings.php:39
+msgid "My Postings"
+msgstr ""
+
+#: lib/Forms/account.php:44 lib/Forms/CreateLedger.php:35
+#: lib/Forms/EditLedger.php:42
+msgid "Name"
+msgstr ""
+
+#: account.php:67 account.php:99
+msgid "New Account"
+msgstr ""
+
+#: templates/data/import.inc:31
+msgid "Next"
+msgstr ""
+
+#: postings.php:648
+msgid "Next Page"
+msgstr ""
+
+#: postings.php:615
+msgid "No Postings"
+msgstr ""
+
+#: templates/reports/empty.inc:2
+msgid "No data for this report."
+msgstr ""
+
+#: lib/Report/GeneralOverview.php:45 lib/Report/Trend.php:50
+#: lib/Report/PeriodOverview.php:44 lib/Report/Analysis.php:51
+#: lib/Report/AccountOverview.php:44 lib/Report/AssetOverview.php:48
+msgid "No display type"
+msgstr ""
+
+#: lib/Report.php:170 lib/ReportGraph.php:175
+msgid "No report driver loaded"
+msgstr ""
+
+#: lib/Block/summary.php:154
+msgid "No results to display"
+msgstr ""
+
+#: templates/prefs/closedperiodselect.inc:4 templates/postings/navbar.inc:10
+msgid "None"
+msgstr ""
+
+#: templates/postings/navbar.inc:15
+#, php-format
+msgid "Not %s"
+msgstr ""
+
+#: lib/Driver/sql.php:133 lib/Driver/sql.php:169 lib/Driver/sql.php:326
+msgid "Not found"
+msgstr ""
+
+#: lib/Forms/account.php:42
+msgid "Number"
+msgstr ""
+
+#: lib/Block/summary.php:44
+msgid "Number of months to display"
+msgstr ""
+
+#: templates/menu.inc:19
+msgid "Open Ledger"
+msgstr ""
+
+#: templates/reports/reports.inc:104
+msgid "Open Report"
+msgstr ""
+
+#: templates/reports/reports.inc:75
+msgid "Other"
+msgstr ""
+
+#: templates/reports/reports.inc:55
+msgid "P_eriod"
+msgstr ""
+
+#: templates/postings/header.inc:18
+#, php-format
+msgid "Page %d of %d"
+msgstr ""
+
+#: lib/Report/PeriodOverview.php:67 lib/Report/AccountOverview.php:73
+msgid "Period"
+msgstr ""
+
+#: config/report.php.dist:10
+msgid "Period Overview"
+msgstr ""
+
+#: templates/postings/transfer.inc:11
+msgid "Period from"
+msgstr ""
+
+#: templates/postings/transfer.inc:50
+msgid "Period to"
+msgstr ""
+
+#: lib/Forms/DeleteLedger.php:55
+msgid "Permission denied"
+msgstr ""
+
+#: templates/postings/javascript_transfer.inc:6
+msgid "Please select another posting type where to transfer postings to."
+msgstr ""
+
+#: templates/postings/posting_headers.inc:26 templates/postings/shift.inc:29
+msgid "Posting A_ccount"
+msgstr ""
+
+#: config/prefs.php.dist:97 config/prefs.php.dist:111
+msgid "Posting Account"
+msgstr ""
+
+#: templates/postings/shift.inc:13
+msgid "Posting T_ype"
+msgstr ""
+
+#: lib/Forms/account.php:81
+msgid "Postings"
+msgstr ""
+
+#: config/prefs.php.dist:75
+msgid "Postings per page in the list view."
+msgstr ""
+
+#: data.php:164
+msgid "Postings successfully purged."
+msgstr ""
+
+#: postings.php:641
+msgid "Previous Page"
+msgstr ""
+
+#: templates/reports/reports.inc:65
+msgid "Re_ference"
+msgstr ""
+
+#: lib/Forms/DeleteLedger.php:42
+#, php-format
+msgid ""
+"Really delete the ledger \"%s\"? This cannot be undone and all data on this "
+"ledger will be permanently removed."
+msgstr ""
+
+#: report.php:96 report.php:99 report.php:101 report.php:102 report.php:103
+#: lib/Report/GeneralOverview.php:81 lib/Report/Analysis.php:83
+#: lib/Report/AccountOverview.php:81
+msgid "Reference"
+msgstr ""
+
+#: templates/postings/header.inc:5
+msgid "Refresh List"
+msgstr ""
+
+#: templates/data/import.inc:18
+msgid ""
+"Replace existing postings with the imported one? <strong>Warning: This "
+"deletes all existing postings.</strong>"
+msgstr ""
+
+#: templates/reports/reports.inc:12
+msgid "Repor_t"
+msgstr ""
+
+#: lib/ReportGraph.php:86 templates/reports/graph.inc:2
+msgid "Report"
+msgstr ""
+
+#: report.php:152
+#, php-format
+msgid "Report %s"
+msgstr ""
+
+#: report.php:111 lib/Block/tree_menu.php:21
+msgid "Reports"
+msgstr ""
+
+#: templates/reports/reports.inc:105
+msgid "Reset"
+msgstr ""
+
+#: lib/Report/Analysis.php:361
+msgid "Rest"
+msgstr ""
+
+#: lib/Report/GeneralOverview.php:61 lib/Report/PeriodOverview.php:76
+#: templates/postings/edit.inc:47
+msgid "Result"
+msgstr ""
+
+#: config/prefs.php.dist:135
+msgid "SQL (% and _)"
+msgstr ""
+
+#: templates/postings/actions.inc:11
+msgid "S_hift"
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:14
+msgid "Saturday"
+msgstr ""
+
+#: lib/Forms/account.php:49 lib/Forms/EditLedger.php:45
+#: templates/postings/actions.inc:15
+msgid "Save"
+msgstr ""
+
+#: account.php:162 lib/Forms/account.php:49
+msgid "Save and New"
+msgstr ""
+
+#: postings.php:267
+#, php-format
+msgid "Saved %d postings."
+msgstr ""
+
+#: account.php:160
+#, php-format
+msgid "Saved %s."
+msgstr ""
+
+#: lib/Fima.php:760 lib/Block/tree_menu.php:19 templates/search/search.inc:106
+#: templates/accounts/accounts.inc:47 templates/postings/header.inc:7
+msgid "Search"
+msgstr ""
+
+#: templates/search/search.inc:43
+msgid "Search A_sset Accounts"
+msgstr ""
+
+#: templates/search/search.inc:77
+msgid "Search Amo_unt Start"
+msgstr ""
+
+#: templates/search/search.inc:85
+msgid "Search Amoun_t End"
+msgstr ""
+
+#: templates/search/search.inc:35
+msgid "Search Dat_e End"
+msgstr ""
+
+#: templates/search/search.inc:69
+msgid "Search Descriptio_n"
+msgstr ""
+
+#: templates/search/search.inc:51
+msgid "Search Posting A_ccounts"
+msgstr ""
+
+#: templates/search/search.inc:15
+msgid "Search Posting T_ype"
+msgstr ""
+
+#: search.php:71 templates/postings/header.inc:7
+msgid "Search Postings"
+msgstr ""
+
+#: postings.php:519
+msgid "Search Results"
+msgstr ""
+
+#: templates/search/search.inc:27
+msgid "Search _Date Start"
+msgstr ""
+
+#: templates/search/search.inc:93
+msgid "Search e.o. Postin_gs"
+msgstr ""
+
+#: templates/postings/navbar.inc:8
+msgid "Select"
+msgstr ""
+
+#: lib/Forms/account.php:146
+msgid "Select another account where to shift postings to."
+msgstr ""
+
+#: lib/Forms/account.php:181
+msgid "Select another account where to shift subaccount postings to."
+msgstr ""
+
+#: config/prefs.php.dist:181
+msgid "Select the canvas size for chart reports:"
+msgstr ""
+
+#: templates/data/import.inc:21
+msgid "Select the charset of the source file:"
+msgstr ""
+
+#: templates/data/export.inc:10
+msgid "Select the export format:"
+msgstr ""
+
+#: templates/data/import.inc:29
+msgid "Select the file to import:"
+msgstr ""
+
+#: config/prefs.php.dist:150
+msgid "Select the format for amounts:"
+msgstr ""
+
+#: config/prefs.php.dist:137
+msgid "Select the format for wildcards for text search:"
+msgstr ""
+
+#: templates/data/import.inc:11
+msgid "Select the format of the source file:"
+msgstr ""
+
+#: lib/Fima.php:432
+msgid "Set date"
+msgstr ""
+
+#: templates/postings/actions.inc:11
+msgid "Shift"
+msgstr ""
+
+#: postings.php:157
+msgid "Shift Postings"
+msgstr ""
+
+#: lib/UI/VarRenderer/fima.php:45
+msgid "Shift postings to"
+msgstr ""
+
+#: postings.php:342
+#, php-format
+msgid "Shifted %d postings."
+msgstr ""
+
+#: templates/postings/shift.inc:3
+msgid "Shifting"
+msgstr ""
+
+#: lib/Block/summary.php:39
+msgid "Show summary of this ledger"
+msgstr ""
+
+#: templates/reports/table.inc:23
+#, php-format
+msgid "Sort by %s"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:35
+msgid "Sort by Amount"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:20
+msgid "Sort by Asset Account"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:15
+msgid "Sort by Date"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:30
+msgid "Sort by Description"
+msgstr ""
+
+#: templates/postings/posting_headers.inc:25
+msgid "Sort by Posting Account"
+msgstr ""
+
+#: config/prefs.php.dist:125
+msgid "Sort direction:"
+msgstr ""
+
+#: config/prefs.php.dist:100
+msgid "Sort postings by:"
+msgstr ""
+
+#: lib/Forms/account.php:82
+msgid "Subaccounts"
+msgstr ""
+
+#: templates/postings/transfer.inc:34
+msgid "Summarize by combining."
+msgstr ""
+
+#: templates/postings/transfer.inc:36
+msgid "Summarize by posting against"
+msgstr ""
+
+#: postings.php:429 postings.php:443 postings.php:454
+msgid "Summarized"
+msgstr ""
+
+#: postings.php:478
+#, php-format
+msgid "Summarized %d postings."
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:8
+msgid "Sunday"
+msgstr ""
+
+#: data.php:26
+msgid "TSV"
+msgstr ""
+
+#: templates/data/export.inc:13 templates/data/import.inc:14
+msgid "Tab separated values"
+msgstr ""
+
+#: data.php:201
+#, php-format
+msgid "The %s file didn't contain any postings."
+msgstr ""
+
+#: lib/Driver.php:72
+msgid "The Finances backend is not currently available."
+msgstr ""
+
+#: lib/Driver.php:364
+#, php-format
+msgid "The Finances backend is not currently available: %s"
+msgstr ""
+
+#: lib/ReportGraph.php:62
+msgid "The Finances report graphs are not currently available."
+msgstr ""
+
+#: lib/ReportGraph.php:185
+#, php-format
+msgid "The Finances report graphs are not currently available: %s"
+msgstr ""
+
+#: lib/Report.php:48
+msgid "The Finances reports are not currently available."
+msgstr ""
+
+#: lib/Report.php:180
+#, php-format
+msgid "The Finances reports are not currently available: %s"
+msgstr ""
+
+#: account.php:121
+#, php-format
+msgid "The account including all postings was shifted from number %s to %s."
+msgstr ""
+
+#: account.php:118
+#, php-format
+msgid "The account number %s is already used by the account %s."
+msgstr ""
+
+#: account.php:131
+#, php-format
+msgid "The account type was set to %s."
+msgstr ""
+
+#: report.php:147
+msgid "The graphs library could not be loaded."
+msgstr ""
+
+#: ledgers/create.php:31
+#, php-format
+msgid "The ledger \"%s\" has been created."
+msgstr ""
+
+#: ledgers/delete.php:48
+#, php-format
+msgid "The ledger \"%s\" has been deleted."
+msgstr ""
+
+#: ledgers/edit.php:42
+#, php-format
+msgid "The ledger \"%s\" has been renamed to \"%s\"."
+msgstr ""
+
+#: ledgers/edit.php:44
+#, php-format
+msgid "The ledger \"%s\" has been saved."
+msgstr ""
+
+#: data.php:162
+#, php-format
+msgid "The postings could not be purged: %s"
+msgstr ""
+
+#: config/prefs.php.dist:114
+msgid "Then:"
+msgstr ""
+
+#: templates/postings/empty.inc:3
+msgid "There are no appropriate postings."
+msgstr ""
+
+#: lib/Report.php:128
+#, php-format
+msgid "There was a problem creating the report graph: %s."
+msgstr ""
+
+#: report.php:129
+#, php-format
+msgid "There was a problem creating the report: %s."
+msgstr ""
+
+#: accounts.php:36
+#, php-format
+msgid "There was a problem deleting all accounts and postings: %s"
+msgstr ""
+
+#: postings.php:389
+#, php-format
+msgid "There was a problem deleting an existing posting: %s"
+msgstr ""
+
+#: postings.php:487
+#, php-format
+msgid "There was a problem deleting an original posting: %s"
+msgstr ""
+
+#: postings.php:298
+#, php-format
+msgid "There was a problem deleting posting #%d: %s"
+msgstr ""
+
+#: account.php:221
+#, php-format
+msgid "There was a problem deleting the account: %s."
+msgstr ""
+
+#: lib/Report.php:135
+#, php-format
+msgid "There was a problem executing the report graph: %s."
+msgstr ""
+
+#: report.php:139
+#, php-format
+msgid "There was a problem executing the report: %s."
+msgstr ""
+
+#: account.php:158
+#, php-format
+msgid "There was a problem saving the account: %s."
+msgstr ""
+
+#: postings.php:244 postings.php:472
+#, php-format
+msgid "There was a problem saving the posting: %s."
+msgstr ""
+
+#: postings.php:335
+#, php-format
+msgid "There was a problem shifting posting #%d: %s"
+msgstr ""
+
+#: data.php:204
+#, php-format
+msgid "There was an error importing the data: %s"
+msgstr ""
+
+#: data.php:103
+msgid "There were no postings to export."
+msgstr ""
+
+#: data.php:137
+msgid "This file format is not supported."
+msgstr ""
+
+#: ledgers/delete.php:24
+msgid "This ledger cannot be deleted."
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:12
+msgid "Thursday"
+msgstr ""
+
+#: lib/Report/Trend.php:79 lib/Report/Trend.php:156
+#: lib/Report/PeriodOverview.php:90 lib/Report/Analysis.php:201
+#: lib/Report/AccountOverview.php:101 lib/Report/AssetOverview.php:76
+#: lib/Block/summary.php:76 templates/postings/list.inc:13
+msgid "Total Result"
+msgstr ""
+
+#: templates/postings/actions.inc:12
+msgid "Trans_fer"
+msgstr ""
+
+#: templates/postings/actions.inc:12
+msgid "Transfer"
+msgstr ""
+
+#: postings.php:165
+msgid "Transfer Postings"
+msgstr ""
+
+#: templates/postings/transfer.inc:3
+msgid "Transfer from"
+msgstr ""
+
+#: templates/postings/transfer.inc:42
+msgid "Transfer to"
+msgstr ""
+
+#: postings.php:478
+#, php-format
+msgid "Transfered %d postings."
+msgstr ""
+
+#: config/report.php.dist:14
+msgid "Trend"
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:10
+msgid "Tuesday"
+msgstr ""
+
+#: lib/Report/GeneralOverview.php:73 lib/Forms/account.php:43
+msgid "Type"
+msgstr ""
+
+#: lib/Forms/DeleteLedger.php:62
+#, php-format
+msgid "Unable to delete \"%s\": %s"
+msgstr ""
+
+#: lib/Report.php:183 lib/ReportGraph.php:188 lib/Driver.php:367
+#, php-format
+msgid "Unable to load the definition of %s."
+msgstr ""
+
+#: lib/Forms/EditLedger.php:54
+#, php-format
+msgid "Unable to save ledger \"%s\": %s"
+msgstr ""
+
+#: postings.php:581 postings.php:590
+msgid "Unknown"
+msgstr ""
+
+#: templates/accounts/accounts.inc:47
+#, php-format
+msgid "View %s"
+msgstr ""
+
+#: templates/postings/javascript_edit.inc:11
+msgid "Wednesday"
+msgstr ""
+
+#: config/prefs.php.dist:86
+msgid "When displaying the postings, which page do you want to start on?"
+msgstr ""
+
+#: ledgers/edit.php:28
+msgid "You are not allowed to change this ledger."
+msgstr ""
+
+#: ledgers/delete.php:35
+msgid "You are not allowed to delete this ledger."
+msgstr ""
+
+#: templates/postings/javascript_list.inc:39
+msgid "You must select at least one posting first."
+msgstr ""
+
+#: templates/prefs/ledgerselect.inc:10
+msgid "Your active ledger:"
+msgstr ""
+
+#: config/prefs.php.dist:55
+msgid "Your active posting type:"
+msgstr ""
+
+#: lib/Fima.php:761
+msgid "_Accounts"
+msgstr ""
+
+#: templates/reports/reports.inc:97
+msgid "_Chart"
+msgstr ""
+
+#: templates/postings/actions.inc:9
+msgid "_Delete"
+msgstr ""
+
+#: templates/postings/transfer.inc:58
+msgid "_Delete existing Postings"
+msgstr ""
+
+#: templates/reports/reports.inc:24
+msgid "_Display"
+msgstr ""
+
+#: templates/postings/actions.inc:10
+msgid "_Edit"
+msgstr ""
+
+#: lib/Fima.php:771
+msgid "_Import/Export"
+msgstr ""
+
+#: templates/postings/transfer.inc:19
+msgid "_Keep original Postings"
+msgstr ""
+
+#: lib/Fima.php:758
+msgid "_List Postings"
+msgstr ""
+
+#: lib/Fima.php:764
+msgid "_My Ledgers"
+msgstr ""
+
+#: templates/reports/reports.inc:88
+msgid "_Null Rows"
+msgstr ""
+
+#: lib/Fima.php:775
+msgid "_Print"
+msgstr ""
+
+#: lib/Fima.php:768
+msgid "_Reports"
+msgstr ""
+
+#: templates/reports/reports.inc:84
+msgid "_Subaccounts"
+msgstr ""
+
+#: templates/postings/transfer.inc:28
+msgid "_Summarize Accounts"
+msgstr ""
+
+#: templates/reports/reports.inc:92
+msgid "_Yearly"
+msgstr ""
+
+#: templates/postings/shift.inc:16 templates/postings/shift.inc:24
+#: templates/postings/shift.inc:32
+msgid "don't change"
+msgstr ""
+
+#: data.php:43 lib/Forms/account.php:45
+#: templates/postings/posting_headers.inc:43
+msgid "e.o."
+msgstr ""
+
+#: postings.php:587
+#, php-format
+msgid "e.o. %s"
+msgstr ""
+
+#: search.php:64
+msgid "e.o. postings only"
+msgstr ""
+
+#: search.php:65
+msgid "no e.o. postings"
+msgstr ""
+
+#: config/prefs.php.dist:136
+msgid "none"
+msgstr ""
+
+#: data.php:111 templates/data/export.inc:1
+msgid "postings.csv"
+msgstr ""
+
+#: data.php:116
+msgid "postings.tsv"
+msgstr ""
diff --git a/fima/postings.php b/fima/postings.php
new file mode 100644 (file)
index 0000000..f579057
--- /dev/null
@@ -0,0 +1,742 @@
+<?php
+/**
+ * $Horde: fima/postings.php,v 1.1 2009/03/11 17:37:00 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Thomas Trethan <thomas@trethan.net>
+ */
+
+@define('FIMA_BASE', dirname(__FILE__));
+require_once FIMA_BASE . '/lib/base.php';
+require_once 'Horde/Variables.php';
+
+$vars = Variables::getDefaultVariables();
+
+/* Get the current action ID. */
+$actionID = Util::getFormData('actionID');
+
+/* Change posting type. */
+if (($postingtype = Util::getFormData('postingtype')) !== null) {
+    $postingtypeold = $prefs->getValue('active_postingtype');
+    $prefs->setValue('active_postingtype', $postingtype);
+}
+
+/* Get closed period. */
+$closedperiod = (int)$prefs->getValue('closed_period');
+
+/* Create page array. */
+$pageOb = array();
+$pageOb['url'] = Horde::applicationUrl('postings.php');
+if (($pageOb['page'] = Util::getFormData('page')) === null) {
+    $pageOb['page'] = $prefs->getValue('startpage');
+}
+$pageOb['mode'] = 'list';
+
+$title = _("My Postings");
+$ledger = Fima::getActiveLedger();
+$filters = array();
+
+switch ($actionID) {
+case 'change_sort':
+    /* Sort out the sorting values. */
+    if (($sortby = Util::getFormData('sortby')) !== null) {
+        $prefs->setValue('sortby', $sortby);
+    }
+    if (($sortdir = Util::getFormData('sortdir')) !== null) {
+        $prefs->setValue('sortdir', $sortdir);
+    }
+    break;
+
+case 'search_postings':
+    /* If we're searching, only list those postings that match the search result. */
+    $_SESSION['fima_search'] = array('type'         => Util::getFormData('search_type'),
+                                     'date_start'   => Util::getFormData('search_date_start'),
+                                     'date_end'     => Util::getFormData('search_date_end'),
+                                     'asset'        => Util::getFormData('search_asset'),
+                                     'account'      => Util::getFormData('search_account'),
+                                     'desc'         => Util::getFormData('search_desc'),
+                                     'amount_start' => Util::getFormData('search_amount_start'),
+                                     'amount_end'   => Util::getFormData('search_amount_end'),
+                                     'eo'           => Util::getFormData('search_eo'));
+
+    /* Build filters. */
+    if ($_SESSION['fima_search']['type'] !== null) {
+        $prefs->setValue('active_postingtype', $_SESSION['fima_search']['type']);
+    }
+    if ($_SESSION['fima_search']['date_start'] !== null) {
+        if (is_array($_SESSION['fima_search']['date_start'])) {
+            $_SESSION['fima_search']['date_start'] = mktime(0, 0, 0, $_SESSION['fima_search']['date_start']['month'],
+                                                                     $_SESSION['fima_search']['date_start']['day'],
+                                                                     $_SESSION['fima_search']['date_start']['year']);
+        } else {
+            $_SESSION['fima_search']['date_start'] = (int)$_SESSION['fima_search']['date_start'];
+        }
+    }
+    if ($_SESSION['fima_search']['date_end'] !== null) {
+        if (is_array($_SESSION['fima_search']['date_end'])) {
+            $_SESSION['fima_search']['date_end']   = mktime(0, 0, 0, $_SESSION['fima_search']['date_end']['month'],
+                                                                     $_SESSION['fima_search']['date_end']['day'],
+                                                                     $_SESSION['fima_search']['date_end']['year']);
+        } else {
+            $_SESSION['fima_search']['date_end'] = (int)$_SESSION['fima_search']['date_end'];
+        }
+    }
+    if ($_SESSION['fima_search']['asset'] !== null) {
+        if (count($_SESSION['fima_search']['asset']) == 0) {
+            unset($_SESSION['fima_search']['asset']);
+        }
+    }
+    if ($_SESSION['fima_search']['account'] !== null) {
+        if (count($_SESSION['fima_search']['account']) == 0) {
+            unset($_SESSION['fima_search']['account']);
+        }
+    }
+    if ($_SESSION['fima_search']['desc'] !== null) {
+        if ($_SESSION['fima_search']['desc'] === '') {
+            unset($_SESSION['fima_search']['desc']);
+        }
+    }
+    if ($_SESSION['fima_search']['amount_start'] !== null) {
+        if ($_SESSION['fima_search']['amount_start'] === '') {
+            unset($_SESSION['fima_search']['amount_start']);
+        }
+    }
+    if ($_SESSION['fima_search']['amount_end'] !== null) {
+        if ($_SESSION['fima_search']['amount_end'] === '') {
+            unset($_SESSION['fima_search']['amount_end']);
+        }
+    }
+    if (isset($_SESSION['fima_search']['amount_start']) && isset($_SESSION['fima_search']['amount_end'])) {
+        if ((double)$_SESSION['fima_search']['amount_start'] > (double)$_SESSION['fima_search']['amount_end']) {
+            $tmp = $_SESSION['fima_search']['amount_start'];
+            $_SESSION['fima_search']['amount_start'] = $_SESSION['fima_search']['amount_end'];
+            $_SESSION['fima_search']['amount_end'] = $tmp;
+        }
+    }
+    if ($_SESSION['fima_search']['eo'] !== null) {
+        if ($_SESSION['fima_search']['eo'] == -1) {
+            unset($_SESSION['fima_search']['eo']);
+        }
+    }
+
+    break;
+
+case 'clear_search':
+    unset($_SESSION['fima_search']);
+    break;
+
+case 'add_postings':
+    $pageOb['mode'] = 'edit';
+    $pageOb['url'] = Util::addParameter($pageOb['url'], 'actionID', 'add_postings');
+    $actionID = 'save_postings';
+    $postings = array();
+    $title = _("Add Postings");
+    break;
+
+case 'edit_postings':
+    $postingset = Util::getFormData('indices');
+    if ($postingset !== null) {
+        $pageOb['mode'] = 'edit';
+        $pageOb['url'] = Util::addParameter($pageOb['url'], 'actionID', 'add_postings');
+        $actionID = 'save_postings';
+        $filters[] = array('id', $postingset);
+        $title = _("Edit Postings");
+    }
+    break;
+
+case 'shift_postings':
+    $postingset = Util::getFormData('indices');
+    if ($postingset !== null) {
+        $pageOb['mode'] = 'shift';
+        $actionID = 'update_postings';
+        $filters[] = array('id', $postingset);
+        $title = _("Shift Postings");
+    }
+    break;
+
+case 'transfer_postings':
+    $pageOb['mode'] = 'transfer';
+    $actionID = 'copymove_postings';
+    $postings = array();
+    $title = _("Transfer Postings");
+    break;
+
+case 'save_postings':
+    /* Get the form values. */
+    $postingset = Util::getFormData('posting_id');
+
+    $share = &$GLOBALS['fima_shares']->getShare($ledger);
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+        $notification->push(sprintf(_("Access denied saving postings to %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('postings.php', true));
+        exit;
+    } elseif ($postingset !== null) {
+        $pageOb['mode'] = 'edit';
+        $title = _("Edit Postings");
+        $posting_owner = $ledger;
+        $posting_type = $prefs->getValue('active_postingtype');
+
+        $posting_dates = Util::getFormData('date');
+        $posting_assets = Util::getFormData('asset');
+        $posting_accounts = Util::getFormData('account');
+        $posting_eos = Util::getFormData('eo');
+        $posting_amounts = Util::getFormData('amount');
+        $posting_descs = Util::getFormData('desc');
+        
+        $postings = array();
+        $savecount = 0;
+       
+        $storage = &Fima_Driver::singleton($ledger);
+        foreach($postingset as $index => $posting_id) {
+            $posting_valid = true;
+            
+            if ($posting_dates[$index] !== '' || $posting_assets[$index] !== '' || $posting_accounts[$index] !== '' ||
+                $posting_amounts[$index] !== '' || $posting_descs[$index] !== '') {
+
+                $posting_date = Fima::convertDateToStamp($posting_dates[$index], Fima::convertDateFormat($prefs->getValue('date_format')));
+                $posting_asset = $posting_assets[$index];
+                $posting_account = $posting_accounts[$index];
+                $posting_eo = (int)(bool)$posting_eos[$index];
+                $posting_amount = Fima::convertAmountToValue($posting_amounts[$index]);
+                $posting_desc = $posting_descs[$index];
+
+                /* Check posting date. */
+                if ($posting_date === false) {
+                    $posting_valid = false;
+                } elseif ($posting_date <= $closedperiod) {
+                    $posting_valid = false;
+                }
+                
+                /* Check asset account and account. */
+                if ($posting_asset === '' || $posting_account === '') {
+                    $posting_valid = false;
+                } elseif ($posting_asset === $posting_account) {
+                    continue;
+                }
+                
+                /* Fix amount sign. */
+                if ($prefs->getValue('expenses_sign') == 0) {
+                    $account = Fima::getAccount($posting_account);
+                    if (!is_a($account, 'PEAR_Error') && $account !== null) {
+                        if ($account['type'] == FIMA_ACCOUNTTYPE_EXPENSE) {
+                            $posting_amount *= -1;
+                        }
+                    }
+                }
+                                
+                /* If $posting_id is set, we're modifying an existing account. Otherwise,
+                 * we're adding a new posting with the provided attributes. */
+                if ($posting_valid) {
+                    if ($posting_id != null) {
+                        $result = $storage->modifyPosting($posting_id, $posting_type, $posting_date, $posting_asset,
+                                                          $posting_account, $posting_eo, $posting_amount, $posting_desc);
+                    } else {
+                        $result = $storage->addPosting($posting_type, $posting_date, $posting_asset, $posting_account,
+                                                       $posting_eo, $posting_amount, $posting_desc);
+                    }
+
+                    // Check our results.
+                    if (is_a($result, 'PEAR_Error')) {
+                        $notification->push(sprintf(_("There was a problem saving the posting: %s."), $result->getMessage()), 'horde.error');
+                        $posting_valid = false;
+                    } else {
+                        $savecount++;
+                    }
+                }
+                
+                /* Reload invalid or unsaved postings. */
+                if (!$posting_valid) {
+                    $postings[] = array('posting_id' => $posting_id,
+                                        'owner' => $ledger,
+                                        'type' => $posting_type,
+                                        'date' => $posting_date,
+                                        'asset' => $posting_asset,
+                                        'account' => $posting_account,
+                                        'eo' => $posting_eo,
+                                        'amount' => $posting_amount,
+                                        'desc' => $posting_desc);
+                }
+            }
+        }    
+
+        if ($savecount > 0) {
+            $notification->push(sprintf(_("Saved %d postings."), $savecount), 'horde.success');
+        }
+        if (count($postings) > 0) {
+            $notification->push(sprintf(_("%d postings not saved."), count($postings)), 'horde.error');
+        } else {
+            /* Return to the posting list. */
+            header('Location: ' . Horde::applicationUrl('postings.php', true));
+            exit;
+        }
+    } else {
+        /* Return to the posting list. */
+        header('Location: ' . Horde::applicationUrl('postings.php', true));
+        exit;
+    }
+    break;
+
+case 'delete_postings':
+    /* Delete postings if we're provided with valid account IDs. */
+    $postingset = Util::getFormData('indices');
+
+    $share = &$GLOBALS['fima_shares']->getShare($ledger);
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_DELETE)) {
+        $notification->push(sprintf(_("Access denied deleting postings from %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('postings.php', true));
+        exit;
+    } elseif ($postingset !== null) {
+        $storage = &Fima_Driver::singleton($ledger);
+        $delcount = 0;
+        foreach($postingset as $index => $posting_id) {
+            $result = $storage->deletePosting($posting_id);
+            if (is_a($result, 'PEAR_Error')) {
+                $notification->push(sprintf(_("There was a problem deleting posting #%d: %s"),
+                                            $index, $result->getMessage()), 'horde.error');
+            } else {
+                $delcount++;
+            }
+        }
+        if ($delcount > 0) {
+            $notification->push(sprintf(_("Deleted %d postings."), $delcount), 'horde.success');
+        }
+    }
+
+    /* Return to the posting list. */
+    header('Location: ' . Horde::applicationUrl('postings.php', true));
+    exit;
+    break;
+
+case 'update_postings':
+    /* Get the form values. */
+    $postingset = Util::getFormData('posting_id');
+
+    $share = &$GLOBALS['fima_shares']->getShare($ledger);
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+        $notification->push(sprintf(_("Access denied shifting postings in %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('postings.php', true));
+        exit;
+    } elseif ($postingset !== null) {
+        $posting_type = Util::getFormData('type');
+        $posting_asset = Util::getFormData('asset');
+        $posting_account = Util::getFormData('account');
+        
+        if ($posting_type || $posting_asset || $posting_account) {
+            $storage = &Fima_Driver::singleton($ledger);
+            $shiftcount = 0;
+
+            foreach($postingset as $index => $posting_id) {
+                $result = $storage->ShiftPosting($posting_id, $posting_type, $posting_asset, $posting_account);
+                if (is_a($result, 'PEAR_Error')) {
+                    $notification->push(sprintf(_("There was a problem shifting posting #%d: %s"),
+                                                  $index, $result->getMessage()), 'horde.error');
+                } else {
+                    $shiftcount++;
+                }
+            }
+            if ($shiftcount > 0) {
+                $notification->push(sprintf(_("Shifted %d postings."), $shiftcount), 'horde.success');
+            }
+        }
+    }
+
+    /* Return to the posting list. */
+    header('Location: ' . Horde::applicationUrl('postings.php', true));
+    exit;
+    break;
+    
+case 'copymove_postings':
+    $share = &$GLOBALS['fima_shares']->getShare($ledger);
+    if (!$share->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+        $notification->push(sprintf(_("Access denied transfering postings in %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('postings.php', true));
+        exit;
+    } elseif (!$share->hasPermission(Auth::getAuth(), PERMS_DELETE) && (!Util::getFormData('keep') || Util::getFormData('delete'))) {
+        $notification->push(sprintf(_("Access denied transfering postings in %s."), $share->get('name')), 'horde.error');
+        header('Location: ' . Horde::applicationUrl('postings.php', true));
+        exit;
+    } else {
+        $type_from = Util::getFormData('type_from');
+        $period_from = Util::getFormData('period_from');
+        $keep = Util::getFormData('keep');
+        $summarize = Util::getFormData('summarize');
+        $summarize_account = Util::getFormData('summarize_post_account');
+        $type_to = Util::getFormData('type_to');
+        $period_to = Util::getFormData('period_to');
+        $delete = Util::getFormData('delete');
+
+        $period_from_start = mktime(0, 0, 0, ($period_from['month'] === '') ? 1 : $period_from['month'], 1, (int)$period_from['year']);
+        $period_from_end = mktime(0, 0, 0, ($period_from['month'] === '') ? 12 : $period_from['month'] + 1, ($period_from['month'] === '') ? 31 : 0, (int)$period_from['year']);
+        $period_to_start = mktime(0, 0, 0, ($period_to['month'] === '') ? 1 : $period_to['month'], 1, (int)$period_to['year']);
+        $period_to_end = mktime(0, 0, 0, ($period_to['month'] === '') ? 12 : $period_to['month'] + 1, ($period_to['month'] === '') ? 31 : 0, (int)$period_to['year']);
+
+        $storage = &Fima_Driver::singleton($ledger);
+
+        /* Delete existing. */
+        if ($delete) {
+            $transferfilters = array(array('type', $type_to),
+                               array('date', $period_to_start, '>='),
+                               array('date', $period_to_end, '<='));
+            $postings = Fima::listPostings($transferfilters);
+            $delcount = 0;
+            foreach ($postings as $postingId => $posting) {
+                $result = $storage->deletePosting($postingId);
+                if (is_a($result, 'PEAR_Error')) {
+                    $notification->push(sprintf(_("There was a problem deleting an existing posting: %s"),
+                                                $result->getMessage()), 'horde.error');
+                } else {
+                    $delcount++;
+                }
+            }
+            if ($delcount > 0) {
+                $notification->push(sprintf(_("Deleted %d existing postings."), $delcount), 'horde.success');
+            }
+        }
+
+        /* Copy postings. */
+        $transferfilters = array(array('type', $type_from),
+                                 array('date', $period_from_start, '>='),
+                                 array('date', $period_from_end, '<='));
+        $postings = Fima::listPostings($transferfilters);
+
+        if ($summarize != 'none') {
+            $accounts = Fima::listAccounts();
+            $postingscopy = array();
+            
+            foreach ($postings as $postingId => $posting) {
+                $asset = (isset($accounts[$posting['asset']]))
+                         ? (($accounts[$posting['asset']]['parent_id'] !== null) ? $accounts[$posting['asset']]['parent_id'] : $accounts[$posting['asset']]['account_id'])
+                         : $posting['asset'];
+                $account = (isset($accounts[$posting['account']]))
+                           ? (($accounts[$posting['account']]['parent_id'] !== null) ? $accounts[$posting['account']]['parent_id'] : $accounts[$posting['account']]['account_id'])
+                           : $posting['account'];
+                
+                if ($summarize == 'combine') {
+                    $copyId = $asset . '_' . $account . '_' . strftime('%Y%m', $posting['date']);
+
+                    if (isset($postingscopy[$copyId])) {
+                        $postingscopy[$copyId]['amount'] += $posting['amount'];
+                    } else {
+                        $postingscopy[$copyId] = $posting;
+                        $postingscopy[$copyId]['date'] = mktime(0, 0, 0, ($period_to['month'] === '') ? strftime('%m', $posting['date']) : $period_to['month'], 1, (int)$period_to['year']);
+                        $postingscopy[$copyId]['asset'] = $asset;
+                        $postingscopy[$copyId]['account'] = $account;
+                        $postingscopy[$copyId]['eo'] = 0;
+                        $postingscopy[$copyId]['desc'] = _("Summarized");
+                    }
+                } elseif ($summarize == 'post') {
+                    $copyIdAsset = $asset . '_' . strftime('%Y%m', $posting['date']);
+                    $copyIdAccount = $account . '_' . strftime('%Y%m', $posting['date']);
+                    
+                    if (isset($postingscopy[$copyIdAsset])) {
+                        $postingscopy[$copyIdAsset]['amount'] += $posting['amount'];
+                    } else {
+                        $postingscopy[$copyIdAsset] = $posting;
+                        $postingscopy[$copyIdAsset]['date'] = mktime(0, 0, 0, ($period_to['month'] === '') ? strftime('%m', $posting['date']) : $period_to['month'], 1, (int)$period_to['year']);
+                        $postingscopy[$copyIdAsset]['asset'] = $asset;
+                        $postingscopy[$copyIdAsset]['account'] = $summarize_account;
+                        $postingscopy[$copyIdAsset]['eo'] = 0;
+                        $postingscopy[$copyIdAsset]['desc'] = _("Summarized");
+                    }
+                    
+                    if (isset($postingscopy[$copyIdAccount])) {
+                        $postingscopy[$copyIdAccount]['amount'] += $posting['amount'];
+                    } else {
+                        $postingscopy[$copyIdAccount] = $posting;
+                        $postingscopy[$copyIdAccount]['date'] = mktime(0, 0, 0, ($period_to['month'] === '') ? strftime('%m', $posting['date']) : $period_to['month'], 1, (int)$period_to['year']);
+                        $postingscopy[$copyIdAccount]['asset'] = $summarize_account;
+                        $postingscopy[$copyIdAccount]['account'] = $account;
+                        $postingscopy[$copyIdAccount]['eo'] = 0;
+                        $postingscopy[$copyIdAccount]['desc'] = _("Summarized");
+                    }
+                }
+            }
+        } else {
+            $postingscopy = &$postings;
+            foreach ($postingscopy as $postingId => $posting) {
+                $postingscopy[$postingId]['date'] = mktime(0, 0, 0, ($period_to['month'] === '') ? strftime('%m', $posting['date']) : $period_to['month'], strftime('%d', $posting['date']), (int)$period_to['year']);
+            }
+        }
+
+        $addcount = 0;
+        foreach ($postingscopy as $postingId => $posting) {
+            $result = $storage->addPosting($type_to, $posting['date'], $posting['asset'], $posting['account'],
+                                           $posting['eo'], $posting['amount'], $posting['desc']);
+
+            // Check our results.
+            if (is_a($result, 'PEAR_Error')) {
+                $notification->push(sprintf(_("There was a problem saving the posting: %s."), $result->getMessage()), 'horde.error');
+            } else {
+                $addcount++;
+            }
+        }
+        if ($addcount > 0) {
+            $notification->push(sprintf($summarize ? _("Summarized %d postings.") : _("Transfered %d postings."), $addcount), 'horde.success');
+        }
+        
+        /* Delete original postings. */
+        if (!$keep) {
+            $delcount = 0;
+            foreach ($postings as $postingId => $posting) {
+                $result = $storage->deletePosting($postingId);
+                if (is_a($result, 'PEAR_Error')) {
+                    $notification->push(sprintf(_("There was a problem deleting an original posting: %s"),
+                                                $result->getMessage()), 'horde.error');
+                } else {
+                    $delcount++;
+                }
+            }
+            if ($delcount > 0) {
+                $notification->push(sprintf(_("Deleted %d original postings."), $delcount), 'horde.success');
+            }
+        }
+    }
+
+    /* Return to the posting list. */
+    header('Location: ' . Horde::applicationUrl('postings.php', true));
+    exit;
+    break;
+    
+default:
+    break;
+}
+
+/* Print. */
+$print_view = (bool)Util::getFormData('print');
+if (!$print_view && $pageOb['mode'] == 'list') {
+    Horde::addScriptFile('popup.js', 'horde', true);
+    $print_link = Util::addParameter(Horde::applicationUrl('postings.php'), array('print' => 1));
+}
+
+/* Filters. */
+$postingtype = $prefs->getValue('active_postingtype');
+$filters[] = array('type', $postingtype);
+if (isset($_SESSION['fima_search'])) {
+    $title = _("Search Results");
+    foreach ($_SESSION['fima_search'] as $searchId => $search) {
+        if ($search === null) {
+            continue;
+        }
+        switch ($searchId) {
+        case 'date_start':   $filters[] = array('date', $search, '>='); break;
+        case 'date_end':     $filters[] = array('date', $search, '<='); break;
+        case 'asset':           $filters[] = array(array(array('asset', $search), array('account', $search, '=', 'OR'))); break;
+        case 'account':      $filters[] = array('account', $search); break;
+        case 'desc':         $filters[] = array('desc', Fima::convertWildcards($search), 'LIKE'); break;
+        case 'amount_start': $filters[] = array('amount', Fima::convertAmountToValue($search), '>='); break;
+        case 'amount_end':   $filters[] = array('amount', Fima::convertAmountToValue($search), '<='); break;
+        case 'eo':           $filters[] = array('eo', (int)(bool)$search);
+        default:             break;
+        }    
+    }
+}
+
+/* Retrieve accounts, accounttypes and postings (if not set before). */
+$accounts = Fima::listAccounts();
+$accounttypes = Fima::getAccountTypes();
+if (!isset($postings)) {
+    $postings = Fima::listPostings($filters, ($pageOb['mode'] != 'list' || $print_view) ? null : $pageOb['page']);
+}
+
+$pageOb['postings_perpage'] = $prefs->getValue('max_postings');
+$pageOb['postings_total'] = Fima::getPostingsCount();
+
+if ($pageOb['mode'] == 'edit') {
+    /* Fix amount sign. */
+    if ($prefs->getValue('expenses_sign') == 0) {
+        foreach ($postings as $postingId => $posting) {
+            if ($accounts[$posting['account']]['type'] == FIMA_ACCOUNTTYPE_EXPENSE) {
+                $postings[$postingId]['amount'] *= -1;
+            }
+        }
+    }
+    /* Add blank postings. */
+    for ($i = count($postings); $i < max($pageOb['postings_perpage'], 12); $i++) {
+        $postings[] = array('posting_id' => null,
+                            'owner' => $ledger,
+                            'type' => $postingtype,
+                            'date' => null,
+                            'asset' => null,
+                            'account' => null,
+                            'eo' => null,
+                            'amount' => null,
+                            'desc' => null);
+    }
+}
+
+/* Add account information to postings and create flags list. */
+if ($pageOb['mode'] == 'list') {
+    $flags = array();
+    foreach ($postings as $postingId => $posting) {
+        $postings[$postingId]['desc'] = htmlspecialchars($posting['desc']);
+        
+        if (isset($accounts[$posting['asset']])) {
+            $postings[$postingId]['asset_label'] = htmlspecialchars($accounts[$posting['asset']]['label']);
+            $postings[$postingId]['asset_closed'] = $accounts[$posting['asset']]['closed'];
+        } else {
+            $postings[$postingId]['asset_label'] = _("Unknown");
+            $postings[$postingId]['asset_closed'] = false;
+        }
+        if (isset($accounts[$posting['account']])) {
+            $postings[$postingId]['account_label'] = htmlspecialchars($accounts[$posting['account']]['label']);
+            $postings[$postingId]['account_type'] = $accounts[$posting['account']]['type'];
+            $postings[$postingId]['account_type_eo'] = sprintf($posting['eo'] ? _("e.o. %s") : _("%s") , $accounttypes[$accounts[$posting['account']]['type']]);
+            $postings[$postingId]['account_closed'] = $accounts[$posting['account']]['closed'];
+        } else {
+            $postings[$postingId]['account_label'] = _("Unknown");
+            $postings[$postingId]['account_type'] = '';
+            $postings[$postingId]['account_type_eo'] = '';
+            $postings[$postingId]['account_closed'] = false;
+        }
+    
+        $flag = 0;
+        $flagpos = 0;
+        foreach ($accounttypes as $typeId => $typeLabel) {
+            if ($postings[$postingId]['account_type'] == $typeId) {
+                $flag |= pow(2, $flagpos);
+            }
+            $flagpos++;
+        }
+        $flags[] = $flag;
+    }
+}
+
+/* Set up page information. */
+$pageOb['page_count'] = ceil($pageOb['postings_total'] / $pageOb['postings_perpage']);
+if ($pageOb['page'] < 0) {
+    $pageOb['page'] += $pageOb['page_count'] + 1;
+}
+if ($pageOb['mode'] == 'list') {
+    if ($pageOb['postings_total'] == 0) {
+        $pageOb['postings_count'] = _("No Postings");
+    } else {
+        $pageOb['postings_count'] = sprintf(_("%s to %s of %s Postings"),
+                                            ($pageOb['page'] - 1) * $pageOb['postings_perpage'] + 1,
+                                            min($pageOb['page'] * $pageOb['postings_perpage'], $pageOb['postings_total']),
+                                            $pageOb['postings_total']); 
+    }
+}
+
+/* Get sorting. */
+if ($pageOb['mode'] == 'list' || $pageOb['mode'] == 'edit') {
+    $sortby = $prefs->getValue('sortby');
+    $sortdir = $prefs->getValue('sortdir');
+    $sorturl = Util::addParameter($pageOb['url'], 'sortdir', ($sortdir) ? 0 : 1);
+}
+
+/* Generate page links. */
+if ($pageOb['mode'] == 'list') {
+    $graphicsdir = $registry->getImageDir('horde');
+    if ($pageOb['page'] == 1) {
+        $pageOb['pages_first'] = Horde::img('nav/first-grey.png', null, null, $graphicsdir);
+        $pageOb['pages_prev'] = Horde::img('nav/left-grey.png', null, null, $graphicsdir);
+    } else {
+        $first_url = Util::addParameter($pageOb['url'], 'page', 1);
+        $pageOb['pages_first'] = Horde::link($first_url, _("First Page")) . Horde::img('nav/first.png', '<<', null, $graphicsdir) . '</a>';
+        $prev_url = Util::addParameter($pageOb['url'], 'page', $pageOb['page'] - 1);
+        $pageOb['pages_prev'] = Horde::link($prev_url, _("Previous Page"), '', '', '', '', '', array('id' => 'prev')) . Horde::img('nav/left.png', '<', null, $graphicsdir) . '</a>';
+    }
+    if ($pageOb['page'] == $pageOb['page_count']) {
+        $pageOb['pages_last'] = Horde::img('nav/last-grey.png', null, null, $graphicsdir);
+        $pageOb['pages_next'] = Horde::img('nav/right-grey.png', null, null, $graphicsdir);
+    } else {
+        $next_url = Util::addParameter($pageOb['url'], 'page', $pageOb['page'] + 1);
+        $pageOb['pages_next'] = Horde::link($next_url, _("Next Page"), '', '', '', '', '', array('id' => 'next')) . Horde::img('nav/right.png', '>', null, $graphicsdir) . '</a>';
+        $last_url = Util::addParameter($pageOb['url'], 'page', $pageOb['page_count']);
+        $pageOb['pages_last'] = Horde::link($last_url, _("Last Page")) . Horde::img('nav/last.png', '>>', null, $graphicsdir) . '</a>';
+    }
+}
+
+/* Some browsers have trouble with hidden overflow in table cells but not in divs. */
+if ($GLOBALS['browser']->hasQuirk('no_hidden_overflow_tables')) {
+    $overflow_begin = '<div class="ohide">';
+    $overflow_end = '</div>';
+} else {
+    $overflow_begin = '';
+    $overflow_end = '';
+}
+
+/* Set up row Ids. */
+$rowId = 0;
+
+/* Get date and amount format. */
+$datefmt = $prefs->getValue('date_format');
+$amountfmt = $prefs->getValue('amount_format');
+
+if ($pageOb['mode'] == 'edit') {
+    /* Fix date format. */
+    $datefmt = Fima::convertDateFormat($datefmt);
+
+    /* Add current date in first field if no postings. */
+    foreach ($postings as $key => $value) {
+        if ($value['date'] == '') { 
+            $notification->push('document.getElementById(\'date1\').value = \'' . strftime($datefmt) . '\';', 'javascript');
+        }
+        break;
+    }
+
+    /* Select first date field. */
+    $notification->push('updateResult(); updateAssetResult(_getall(\'asset[]\')[0]); document.getElementById(\'date1\').focus(); document.getElementById(\'date1\').select();', 'javascript');
+}
+
+require FIMA_TEMPLATES . '/common-header.inc';
+if ($print_view) {
+    require_once $registry->get('templates', 'horde') . '/javascript/print.js';
+} else {
+    require FIMA_TEMPLATES . '/menu.inc';
+}
+if ($browser->hasFeature('javascript')) {
+    require FIMA_TEMPLATES . '/postings/javascript_' . $pageOb['mode'] . '.inc';
+}
+
+/* Get current asset results. */
+if ($pageOb['mode'] == 'edit') {
+    $assetresults = Fima::getAssetResults($prefs->getValue('active_postingtype'));
+}
+
+/* Generate tabs. */
+if ($pageOb['mode'] != 'transfer' && !$print_view) {
+    require_once 'Horde/UI/Tabs.php';
+    $tabs = new Horde_UI_Tabs('postingtype', $vars);
+    $postingtypes = Fima::getPostingTypes();
+    foreach ($postingtypes as $typeValue => $typeLabel) {
+        $tabs->addTab($typeLabel, $pageOb['url'], $typeValue);
+    }
+    echo $tabs->render($prefs->getValue('active_postingtype'));
+}
+
+/* Generate list. */
+if (!$print_view) {
+    require FIMA_TEMPLATES . '/postings/header.inc';
+}
+
+if ($pageOb['mode'] == 'list' && $pageOb['page_count'] == 0) {
+    require FIMA_TEMPLATES . '/postings/empty.inc';
+} else {
+    $form = 1;
+    if (!$print_view) {
+        require FIMA_TEMPLATES . '/postings/navbar.inc';
+        require FIMA_TEMPLATES . '/postings/actions.inc';
+    }
+
+    require FIMA_TEMPLATES . '/postings/posting_headers.inc';
+    require FIMA_TEMPLATES . '/postings/' . $pageOb['mode'] . '.inc';
+    require FIMA_TEMPLATES . '/postings/posting_footers.inc';
+
+    /* If there are 20 postings or less, don't show the actions/navbar again. */
+    if ((count($postings) > 20 || $pageOb['mode'] != 'list') && !$print_view) {
+        $form = 2;
+        require FIMA_TEMPLATES . '/postings/actions.inc';
+        require FIMA_TEMPLATES . '/postings/navbar.inc';
+    } else {
+        /* TODO */
+        echo '<tr><td class="control" colspan="6"></td></tr>';
+    }
+}
+require FIMA_TEMPLATES . '/postings/footer.inc';
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/report.php b/fima/report.php
new file mode 100644 (file)
index 0000000..53808a9
--- /dev/null
@@ -0,0 +1,179 @@
+<?php
+/**
+ * $Horde: fima/report.php,v 1.1 2009/03/12 13:28:05 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file COPYING for license information (GPL).  If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('FIMA_BASE', dirname(__FILE__));
+require_once FIMA_BASE . '/lib/base.php';
+require_once FIMA_BASE . '/config/report.php';
+require_once FIMA_BASE . '/lib/Report.php';
+
+$actionID = Util::getFormData('actionID');
+
+switch ($actionID) {
+case 'open_report':
+    $_SESSION['fima_report'] = array('report_id'       => Util::getFormData('report_id'),
+                                     'display'            => Util::getFormData('display'),
+                                     'posting_account' => Util::getFormData('posting_account'),
+                                     'period_start'    => Util::getFormData('period_start'),
+                                     'period_end'      => Util::getFormData('period_end'),
+                                     'reference_start' => Util::getFormData('reference_start'),
+                                     'reference_end'   => Util::getFormData('reference_end'),
+                                     'cumulate'        => Util::getFormData('cumulate'),
+                                     'nullrows'        => Util::getFormData('nullrows'),
+                                     'subaccounts'     => Util::getFormData('subaccounts'),
+                                     'yearly'          => Util::getFormData('yearly'),
+                                     'graph'           => Util::getFormData('graph'));
+    break;
+case 'clear_report':
+    unset($_SESSION['fima_report']);
+    break;
+default:
+    break;
+}
+
+/* Create params array. */
+$params = isset($_SESSION['fima_report']) ? $_SESSION['fima_report'] : array();
+
+/* Set initial values. */
+if (!isset($params['report_id'])) {
+    $params['report_id'] = '';
+}
+if (!isset($params['display'])) {
+    $params['display'] = '';
+}
+if (!isset($params['posting_account'])) {
+    $params['posting_account'] = array();
+}
+if (!isset($params['period_start'])) {
+    $params['period_start'] = mktime(0, 0, 0, 1, 1);
+} elseif (is_array($params['period_start'])) {
+    $params['period_start'] = mktime(0, 0, 0, $params['period_start']['month'], 1, $params['period_start']['year']);
+}
+if (!isset($params['period_end'])) {
+    $params['period_end'] = mktime(0, 0, 0, 12, 31);
+} elseif (is_array($params['period_end'])) {
+    $params['period_end'] = mktime(0, 0, 0, $params['period_end']['month'] + 1, 1, $params['period_end']['year']) - 1;
+}
+if (!isset($params['reference_start'])) {
+    $params['reference_start'] = mktime(0, 0, 0, 1, 1, date('Y') - 1);
+} elseif (is_array($params['reference_start'])) {
+    $params['reference_start'] = mktime(0, 0, 0, $params['reference_start']['month'], 1, $params['reference_start']['year']);
+}
+if (!isset($params['reference_end'])) {
+    $params['reference_end'] = mktime(0, 0, 0, 12, 31, date('Y') - 1);
+} elseif (is_array($params['reference_end'])) {
+    $params['reference_end'] = mktime(0, 0, 0, $params['reference_end']['month'] + 1, 1, $params['reference_end']['year']) - 1;
+}
+if (!isset($params['cumulate'])) {
+    $params['cumulate'] = 0;
+}
+if (!isset($params['nullrows'])) {
+    $params['nullrows'] = 0;
+}
+if (!isset($params['subaccounts'])) {
+    $params['subaccounts'] = 0;
+}
+if (!isset($params['yearly'])) {
+    $params['yearly'] = 0;
+}
+if (!isset($params['graph'])) {
+    $params['graph'] = 0;
+}
+
+$params['out'] = Util::getFormData('out');
+$params['sortby']  = Util::getFormData('sortby');
+$params['sortdir'] = Util::getFormData('sortdir');
+
+/* Get posting types and output displays. */
+$types = Fima::getPostingTypes();
+$displaylabel = _("%s [%s - %s - %s]");
+$displays = array(FIMA_POSTINGTYPE_ACTUAL.'_'.FIMA_POSTINGTYPE_FORECAST.'_'.FIMA_POSTINGTYPE_BUDGET.'_reference' => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_ACTUAL],   $types[FIMA_POSTINGTYPE_FORECAST], $types[FIMA_POSTINGTYPE_BUDGET], _("Reference")),
+                  FIMA_POSTINGTYPE_ACTUAL.'_'.FIMA_POSTINGTYPE_FORECAST.'_difference_%'                          => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_ACTUAL],   $types[FIMA_POSTINGTYPE_FORECAST], _("Difference"),                 _("%")),
+                  FIMA_POSTINGTYPE_ACTUAL.'_'.FIMA_POSTINGTYPE_BUDGET.'_difference_%'                            => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_ACTUAL],   $types[FIMA_POSTINGTYPE_BUDGET],   _("Difference"),                 _("%")),
+                  FIMA_POSTINGTYPE_ACTUAL.'_reference_difference_%'                                              => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_ACTUAL],   _("Reference"),                    _("Difference"),                 _("%")),
+                  FIMA_POSTINGTYPE_FORECAST.'_'.FIMA_POSTINGTYPE_BUDGET.'_difference_%'                          => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_FORECAST], $types[FIMA_POSTINGTYPE_BUDGET],   _("Difference"),                 _("%")),
+                  FIMA_POSTINGTYPE_FORECAST.'_reference_difference_%'                                            => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_FORECAST], _("Reference"),                    _("Difference"),                 _("%")),
+                  FIMA_POSTINGTYPE_BUDGET.'_reference_difference_%'                                              => sprintf($displaylabel, $types[FIMA_POSTINGTYPE_BUDGET],   _("Reference"),                    _("Difference"),                 _("%")),
+                  'reference_'.FIMA_POSTINGTYPE_ACTUAL.'_difference_%'                                           => sprintf($displaylabel, _("Reference"),                    $types[FIMA_POSTINGTYPE_ACTUAL],   _("Difference"),                 _("%")));
+
+/* Include graphs library. */
+$error_reporting = ini_get('error_reporting');
+ini_set('error_reporting', $error_reporting & ~E_WARNING);
+$graphs = include_once 'Image/Graph.php';
+ini_set('error_reporting', $error_reporting);
+
+$title = _("Reports");
+
+switch ($actionID) {
+case 'open_report':
+case 'display_report':
+    if ($params['report_id'] !== null) {
+        /* Title. */
+        $params['title'] = $_reports[$params['report_id']];
+        
+        /* Build report url. */
+        $params['url'] = Util::addParameter(Horde::applicationUrl('report.php'), 'actionID', 'display_report');
+
+        /* Add params from options. */
+        $params['graphsize'] = $prefs->getValue('report_graphsize');
+
+        /* Execute report. */
+        $report = &Fima_Report::factory($params['report_id'], $params);
+        if (is_a($report, 'PEAR_Error')) {
+            $notification->push(sprintf(_("There was a problem creating the report: %s."), $report->getMessage()), 'horde.error');
+            break;
+        }
+            
+        if ($params['graph'] && !$params['out']) {
+            break;
+        }
+
+        $status = $report->execute();
+        if (is_a($status, 'PEAR_Error')) {
+            $notification->push(sprintf(_("There was a problem executing the report: %s."), $status->getMessage()), 'horde.error');
+            break;
+        }
+        
+        if ($params['graph']) {
+            if ($graphs) {
+                require FIMA_TEMPLATES . '/reports/img.inc';
+            } else {
+                $notification->push(_("The graphs library could not be loaded."), 'horde.error');
+            }
+            exit;
+        }
+
+        $title = sprintf(_("Report %s"), $params['title']);
+    }
+    break;
+default:
+    break;
+}
+
+/* Get date and amount format. */
+$datefmt = $prefs->getValue('date_format');
+$amountfmt = $prefs->getValue('amount_format');
+
+$notification->push('document.report.report_id.focus();', 'javascript');
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+if ($browser->hasFeature('javascript')) {
+    require FIMA_TEMPLATES . '/postings/javascript_edit.inc';
+}
+require FIMA_TEMPLATES . '/reports/reports.inc';
+if (isset($report)) {
+    if ($params['graph']) {
+        require FIMA_TEMPLATES . '/reports/graph.inc';
+    } elseif (count($report->getData()) != 0) {
+        require FIMA_TEMPLATES . '/reports/table.inc';
+    } else {
+        require FIMA_TEMPLATES . '/reports/empty.inc';
+    }
+}
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/scripts/sql/fima.sql b/fima/scripts/sql/fima.sql
new file mode 100644 (file)
index 0000000..010046b
--- /dev/null
@@ -0,0 +1,42 @@
+-- $Horde: fima/scripts/sql/fima.sql,v 1.0 2008/04/28 09:03:39 trt Exp $
+
+CREATE TABLE fima_accounts (
+    account_id      VARCHAR(32) NOT NULL,
+    account_owner   VARCHAR(255) NOT NULL,
+    account_number  VARCHAR(4) NOT NULL,
+    account_type    VARCHAR(255) NOT NULL,
+    account_name    VARCHAR(255) NOT NULL,
+    account_desc    TEXT NOT NULL,
+    account_eo      INT NOT NULL DEFAULT 0,
+    account_closed  INT NOT NULL DEFAULT 0,
+--
+    PRIMARY KEY (account_id)
+);
+
+CREATE INDEX fima_account_owner_idx ON fima_accounts (account_owner);
+CREATE INDEX fima_account_type_idx ON fima_accounts (account_type);
+
+GRANT SELECT, INSERT, UPDATE, DELETE ON fima_accounts TO horde;
+
+CREATE TABLE fima_postings (
+    posting_id       VARCHAR(32) NOT NULL,
+    posting_owner    VARCHAR(255) NOT NULL,
+    posting_type     VARCHAR(255) NOT NULL,
+    posting_date     INT NOT NULL,
+    posting_asset    VARCHAR(32) NOT NULL,
+    posting_account  VARCHAR(32) NOT NULL,
+    posting_eo       INT NOT NULL DEFAULT 0,
+    posting_amount   DECIMAL(10,2) NOT NULL,
+    posting_desc     VARCHAR(255) NOT NULL,
+--
+    PRIMARY KEY (posting_id)
+);
+
+CREATE INDEX fima_posting_owner_idx ON fima_postings (posting_owner);
+CREATE INDEX fima_posting_type_idx ON fima_postings (posting_type);
+CREATE INDEX fima_posting_account_idx ON fima_postings (posting_account);
+CREATE INDEX fima_posting_asset_idx ON fima_postings (posting_asset);
+
+
+GRANT SELECT, INSERT, UPDATE, DELETE ON fima_postings TO horde;
+
diff --git a/fima/search.php b/fima/search.php
new file mode 100644 (file)
index 0000000..2a4c25e
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * $Horde: fima/search.php,v 1.0 2008/09/23 22:18:05 trt Exp $
+ *
+ * Copyright 2008 Thomas Trethan <thomas@trethan.net>
+ *
+ * See the enclosed file COPYING for license information (GPL).  If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('FIMA_BASE', dirname(__FILE__));
+require_once FIMA_BASE . '/lib/base.php';
+
+/* Get the current action ID. */
+$actionID = Util::getFormData('actionID');
+
+switch ($actionID) {
+case 'clear_search':
+    unset($_SESSION['fima_search']);
+    break;
+default:
+    break;
+}
+
+/* Get search array. */
+$search = isset($_SESSION['fima_search']) ? $_SESSION['fima_search'] : array();
+
+/* Set initial values. */
+if (!isset($search['type'])) {
+    $search['type'] = $prefs->getValue('active_postingtype');
+}
+if (!isset($search['date_start'])) {
+    $search['date_start'] = mktime(0, 0, 0, 1, 1);
+} elseif (is_array($search['date_start'])) {
+    $search['date_start'] = mktime(0, 0, 0, $search['date_start']['month'], $search['date_start']['day'], (int)$search['date_start']['year']);
+}
+if (!isset($search['date_end'])) {
+    $search['date_end'] = mktime(0, 0, 0, 12, 31);
+} elseif (is_array($search['date_end'])) {
+    $search['date_end'] = mktime(0, 0, 0, $search['date_end']['month'], $search['date_end']['day'], (int)$search['date_end']['year']);
+}
+if (!isset($search['asset'])) {
+    $search['asset'] = array();
+}
+if (!isset($search['account'])) {
+    $search['account'] = array();
+}
+if (!isset($search['desc'])) {
+    $search['desc'] = '';
+}
+if (!isset($search['amount_start'])) {
+    $search['amount_start'] = '';
+}
+if (!isset($search['amount_end'])) {
+    $search['amount_end'] = '';
+}
+if (!isset($search['eo'])) {
+    $search['eo'] = -1;
+}
+
+/* Get posting types and eo. */
+$types = Fima::getPostingTypes();
+$eos = array('-1' => '',
+             '1'  => _("e.o. postings only"),
+             '0'  => _("no e.o. postings"));
+
+/* Get date and amount format. */
+$datefmt = $prefs->getValue('date_format');
+$amountfmt = $prefs->getValue('amount_format');
+
+$title = _("Search Postings");
+$notification->push('document.search.search_type.focus();', 'javascript');
+require FIMA_TEMPLATES . '/common-header.inc';
+require FIMA_TEMPLATES . '/menu.inc';
+if ($browser->hasFeature('javascript')) {
+    require FIMA_TEMPLATES . '/postings/javascript_edit.inc';
+}
+require FIMA_TEMPLATES . '/search/search.inc';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/fima/templates/accounts/accounts.inc b/fima/templates/accounts/accounts.inc
new file mode 100644 (file)
index 0000000..f314d53
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+$perm_edit = $share->hasPermission(Auth::getAuth(), PERMS_EDIT);
+$perm_delete = $share->hasPermission(Auth::getAuth(), PERMS_DELETE);
+
+?>
+<script type="text/javascript">
+<!--
+function Submit(action)
+{
+    if (action == "delete_all") {
+        if (!confirm("<?php echo _("Are you sure you wish to PERMANENTLY delete all accounts and all postings?") ?>")) {
+            return;
+        }
+    }
+    
+    document.accounts.actionID.value = action;
+    document.accounts.submit();
+}
+//-->
+</script>
+
+<h1 class="header"><?php echo htmlspecialchars($share->get('name')) ?></h1>
+
+<form method="post" name="accounts" action="accounts.php">
+<?php Util::pformInput() ?>
+<input type="hidden" name="actionID" value="" />
+
+<div id="accountList">
+<?php
+
+$params = array('icondir' => $GLOBALS['registry']->getImageDir(), 'icon' => '');
+$tree = Horde_Tree::factory('account_tree', 'javascript');
+
+foreach ($accounts as $accountId => $account) {
+    $params['icon'] = $account['icon'];
+    $accountLabel = htmlspecialchars(trim($account['number'] . ' ' . $account['name']));
+    if ($account['closed']) {
+        $accountLabel = '<span class="closed">' . $accountLabel . '</span>';
+    }
+    $accountExtra = '';
+    if (!$print_view) {
+        if ($perm_delete && $accountId == 'root')           { $accountExtra .= '&nbsp;'.Horde::link('#', _("Delete all"), '', '', "Submit('delete_all'); return false;")                    . Horde::img('delete-small.png', _("Delete"), '', $GLOBALS['registry']->getImageDir('horde')) . '</a>'; }
+        if ($perm_edit   && isset($account['edit_link']))   { $accountExtra .= '&nbsp;'.Horde::link(Horde::applicationUrl($account['edit_link']),   sprintf(_("Edit %s"), $accountLabel))   . Horde::img('edit-small.png', _("Edit"), '', $GLOBALS['registry']->getImageDir('horde'))     . '</a>'; }
+        if ($perm_delete && isset($account['delete_link'])) { $accountExtra .= '&nbsp;'.Horde::link(Horde::applicationUrl($account['delete_link']), sprintf(_("Delete %s"), $accountLabel)) . Horde::img('delete-small.png', _("Delete"), '', $GLOBALS['registry']->getImageDir('horde')) . '</a>'; }
+        if ($perm_edit   && isset($account['add_link']))    { $accountExtra .= '&nbsp;'.Horde::link(Horde::applicationUrl($account['add_link']), _("Create a New Account"))                 . Horde::img('new-small.png', '+')                                                            . '</a>'; }
+        if (                isset($account['view_link']))   { $accountExtra .= '&nbsp;'.Horde::link(Horde::applicationUrl($account['view_link']), sprintf(_("View %s"), $accountLabel))     . Horde::img('search-small.png', _("Search"))                                                 . '</a>'; }
+    }
+    $tree->addNode($accountId, $account['parent_id'], $accountLabel, 0, $print_view ? true : $account['expanded'], $params, $accountExtra);
+}
+
+echo $tree->renderTree();
+
+?>
+</div>
+</form>
diff --git a/fima/templates/common-header.inc b/fima/templates/common-header.inc
new file mode 100644 (file)
index 0000000..f72afa2
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+if (isset($language)) {
+    header('Content-type: text/html; charset=' . NLS::getCharset());
+    header('Vary: Accept-Language');
+}
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
+<!-- Fima: Copyright 2007-2008 The Horde Project. Fima is under the GPL.          -->
+<!--     Horde Project: http://www.horde.org/ | Fima: http://www.horde.org/fima/  -->
+<!--                 Horde Licenses: http://www.horde.org/licenses/               -->
+<?php echo !empty($language) ? '<html lang="' . strtr($language, '_', '-') . '">' : '<html>' ?>
+<head>
+<?php
+
+$page_title = $registry->get('name');
+if (!empty($title)) $page_title .= ' :: ' . $title;
+if (!empty($refresh_time) && ($refresh_time > 0) && !empty($refresh_url)) {
+    echo "<meta http-equiv=\"refresh\" content=\"$refresh_time;url=$refresh_url\">\n";
+}
+
+Horde::includeScriptFiles();
+
+?>
+<title><?php echo htmlspecialchars($page_title) ?></title>
+<link href="<?php echo $GLOBALS['registry']->getImageDir()?>/favicon.ico" rel="SHORTCUT ICON" />
+<?php echo Horde::stylesheetLink('fima', empty($print_view) ? '' : 'print') ?>
+</head>
+
+<body<?php if ($bc = Util::nonInputVar('bodyClass')) echo ' class="' . $bc . '"' ?><?php if ($bi = Util::nonInputVar('bodyId')) echo ' id="' . $bi . '"'; ?>>
diff --git a/fima/templates/data/export.inc b/fima/templates/data/export.inc
new file mode 100644 (file)
index 0000000..4940ad9
--- /dev/null
@@ -0,0 +1,18 @@
+<form method="post" name="export" action="<?php echo Horde::downloadUrl(_("postings.csv"), null, Horde::applicationUrl('data.php')) ?>">
+<?php echo Util::formInput() ?>
+<input type="hidden" name="actionID" value="export" />
+
+<h1 class="header">
+ <?php echo _("Export Postings") ?>
+</h1>
+
+<div class="item">
+ <?php echo Horde::label('exportID', _("Select the export format:")) ?><br />
+ <select name="exportID" id="exportID">
+  <option value="<?php echo EXPORT_CSV ?>"><?php echo _("Comma separated values") ?></option>
+  <option value="<?php echo EXPORT_TSV ?>"><?php echo _("Tab separated values") ?></option>
+ </select><br />
+
+ <input type="submit" value="<?php echo _("Export") ?>" class="button" />
+</div>
+</form>
diff --git a/fima/templates/data/import.inc b/fima/templates/data/import.inc
new file mode 100644 (file)
index 0000000..ca8a432
--- /dev/null
@@ -0,0 +1,35 @@
+<form method="post" name="import_form" enctype="multipart/form-data" action="data.php">
+<?php Util::pformInput() ?>
+<input type="hidden" name="actionID" value="<?php echo htmlspecialchars($next_step) ?>" />
+<input type="hidden" name="import_step" value="<?php echo (int)$import_step ?>" />
+
+<h1 class="header">
+ <?php printf(_("Import Postings, Step %d"), (int)$import_step) ?>
+</h1>
+
+<div class="item">
+ <?php echo Horde::label('import_format', _("Select the format of the source file:")) ?><br />
+ <select name="import_format" id="import_format">
+  <option value="csv"><?php echo _("Comma separated values") ?></option>
+  <option value="tsv"><?php echo _("Tab separated values") ?></option>
+ </select><br />
+
+ <input type="checkbox" align="middle" name="purge" id="purge" value="1" />
+ <?php echo Horde::label('purge', _("Replace existing postings with the imported one? <strong>Warning: This deletes all existing postings.</strong>")) ?>
+ <br />
+
+ <?php echo Horde::label('charset', _("Select the charset of the source file:")) ?><br />
+ <select name="charset" id="charset" style="direction: ltr">
+<?php foreach ($charsets as $charset => $charset_name): ?>
+  <option value="<?php echo htmlspecialchars($charset) ?>"<?php if ($charset == $my_charset) echo ' selected="selected"' ?>><?php echo htmlspecialchars($charset_name) ?></option>
+<?php endforeach; ?>
+ </select>
+ <br /><br />
+
+ <?php echo Horde::label('import_file', _("Select the file to import:")) ?><br />
+ <input type="file" name="import_file" id="import_file" size="40" /><br /><br />
+ <input type="submit" value="<?php echo _("Next") ?>" class="button" />
+</div>
+</form>
+
+<br class="spacer" />
diff --git a/fima/templates/ledgers_list.php b/fima/templates/ledgers_list.php
new file mode 100644 (file)
index 0000000..d77e27a
--- /dev/null
@@ -0,0 +1,39 @@
+<h1 class="header">
+ <?php echo _("Manage Ledgers") ?>
+</h1>
+
+<div id="ledger-list-buttons">
+ <form method="get" action="create.php">
+<?php echo Util::formInput() ?>
+  <input type="submit" class="button" value="<?php echo _("Create a new Ledger") ?>" />
+ </form>
+</div>
+
+<table summary="<?php echo _("Ledger List") ?>" cellspacing="0" id="ledger-list" class="striped sortable">
+ <thead>
+  <tr>
+   <th class="ledger-list-icon nosort"><?php echo $browse_img ?></th>
+<th class="sortdown"><?php echo _("Ledger") ?></th>
+   <th class="ledger-list-icon nosort"><?php echo $edit_img ?></th>
+<?php if (empty($conf['share']['no_sharing'])): ?>
+   <th class="ledger-list-icon nosort"><?php echo $perms_img ?></th>
+<?php endif; ?>
+   <th class="ledger-list-icon nosort"><?php echo $delete_img ?></th>
+  </tr>
+ </thead>
+
+ <tbody>
+<?php foreach (array_keys($sorted_ledgers) as $ledger_id): ?>
+ <?php $ledger = $ledgers[$ledger_id] ?>
+  <tr>
+   <td><?php echo $browse_img ?></td>
+   <td><?php echo htmlspecialchars($ledger->get('name')) ?></td>
+   <td><a href="<?php echo Util::addParameter($edit_url_base, 'l', $ledger->getName()) ?>" title="<?php echo _("Edit") ?>"><?php echo $edit_img ?></a></td>
+<?php if (empty($conf['share']['no_sharing'])): ?>
+   <td><a onclick="return !popup(this.href);" href="<?php echo Util::addParameter($perms_url_base, 'share', $ledger->getName()) ?>" target="_blank" title="<?php echo _("Change Permissions") ?>"><?php echo $perms_img ?></a></td>
+<?php endif; ?>
+   <td><a href="<?php echo Util::addParameter($delete_url_base, 'l', $ledger->getName()) ?>" title="<?php echo _("Delete") ?>"><?php echo $delete_img ?></a></td>
+  </tr>
+<?php endforeach; ?>
+ </tbody>
+</table>
diff --git a/fima/templates/menu.inc b/fima/templates/menu.inc
new file mode 100644 (file)
index 0000000..2eda29f
--- /dev/null
@@ -0,0 +1,40 @@
+<form action="<?php echo basename($_SERVER['PHP_SELF']) ?>" method="get" name="menuform">
+<input type="hidden" name="actionID" value="change_ledger" />
+<div id="menu">
+ <div class="rightFloat">
+  <label for="changeledger">
+  <select id="changeledger" name="changeledger" onchange="ledgerSubmit()">
+<?php
+$ledgers = Fima::listLedgers();
+$activeLedger = Fima::getActiveLedger();
+foreach ($ledgers as $ledgerId => $ledger) {
+    $selected = ($ledgerId == $activeLedger) ? ' selected="selected"' : '';
+    echo '   <option value="' . $ledgerId . '"' . $selected .'>' . $ledger->get('name') . '</option>' . "\n";
+}
+?>
+  </select>
+  </label>
+ </div>
+ <div class="rightFloat">
+  <ul><li class="rightFloat"><a href="#" onclick="ledgerSubmit(true); return false;"><?php echo Horde::img('list.png', _("Open Ledger"), '') . '<br />' . _("Open Ledger") ?></a></li></ul>
+ </div>
+
+ <?php echo Fima::getMenu('string') ?>
+ <br class="clear" />
+
+</div>
+</form>
+
+<script type="text/javascript">
+var loading;
+function ledgerSubmit(clear)
+{
+    if (document.menuform.changeledger[document.menuform.changeledger.selectedIndex].value != '') {
+        if ((loading == null) || (clear != null)) {
+            loading = true;
+            document.menuform.submit();
+        }
+    }
+}
+</script>
+<?php $GLOBALS['notification']->notify(array('listeners' => 'status')) ?>
diff --git a/fima/templates/postings/actions.inc b/fima/templates/postings/actions.inc
new file mode 100644 (file)
index 0000000..7a783a7
--- /dev/null
@@ -0,0 +1,23 @@
+<table width="100%" cellspacing="0">
+ <tr>
+  <td class="mboxcontrol <?php if ($form == 1) { echo 'bottombordercollapse'; } else { echo 'topborder'; } ?>">
+   <div class="rightFloat">
+   </div>
+   <div class="leftFloat">
+<?php if ($pageOb['mode'] == 'list'): ?>
+    <ul class="msgactions">
+     <li><?php echo Horde::widget('#', _("Delete"), 'widget', '', "Submit('delete_postings'); return false;", _("_Delete")); ?></li>
+     <li><?php echo Horde::widget('#', _("Edit"), 'widget', '', "Submit('edit_postings'); return false;", _("_Edit")); ?></li>
+     <li><?php echo Horde::widget('#', _("Shift"), 'widget', '', "Submit('shift_postings'); return false;", _("S_hift")); ?></li>
+     <li class="lastnavbar"><?php echo Horde::widget('#', _("Transfer"), 'widget', '', "Submit('transfer_postings'); return false;", _("Trans_fer")); ?></li>
+    </ul>
+<?php else: ?>
+    <input type="submit" class="button" value="<?php echo _("Save") ?>" onclick="return Submit('<?php echo $actionID ?>');" />
+<?php if (in_array($prefs->getValue('active_postingtype'), array(FIMA_POSTINGTYPE_FORECAST, FIMA_POSTINGTYPE_BUDGET))): ?>
+    <input type="button" class="button" value="<?php echo _("Autofill") ?>" onclick="autofillPostings();" />
+<?php endif; ?>
+<?php endif; ?>
+   </div>
+  </td>
+ </tr>
+</table>
diff --git a/fima/templates/postings/edit.inc b/fima/templates/postings/edit.inc
new file mode 100644 (file)
index 0000000..f4a9dc0
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/* Cache created widgets. */
+$assetWidgets = array(-1 => Fima::buildAccountWidget('asset[]', '', 'onfocus="updateAccount(this);" onblur="updateAssetResult(this);"', '', false, array(array('type', FIMA_ACCOUNTTYPE_ASSET)), true));
+$accountWidgets = array(-1 => Fima::buildAccountWidget('account[]', '', 'onblur="updateEo(this);"', '', false, array(), true));
+?>
+<?php foreach($postings as $postingId => $posting): ?>
+<?php $rowId++; ?>
+ <tr id="row<?php echo $rowId ?>" class="posting">
+  <td>
+   <input name="posting_id[]" type="hidden" value="<?php echo $posting['posting_id'] ?>" />
+   <input name="date[]" id="date<?php echo $rowId ?>" type="text" value="<?php echo $posting['date'] == '' ? '' : strftime($datefmt, $posting['date']) ?>" onfocus="updateDate(this);" onblur="formatDate(this);" size="10" maxlength="10" />
+  </td>
+  <td class="ohide">
+<?php
+echo $overflow_begin;
+$a = ($posting['asset'] == '') ? -1 : $posting['asset'];
+if (!isset($assetWidgets[$a])) { 
+    $assetWidgets[$a] = Fima::buildAccountWidget('asset[]', $posting['asset'], 'onblur="updateAssetResult(this);"', false, false, array(array('type', FIMA_ACCOUNTTYPE_ASSET)), true);
+}
+echo $assetWidgets[$a];
+echo $overflow_end;
+?>
+  <td class="ohide">
+<?php
+echo $overflow_begin;
+$a = ($posting['account'] == '') ? -1 : $posting['account'];
+if (!isset($accountWidgets[$a])) {
+    $accountWidgets[$a] = Fima::buildAccountWidget('account[]', $posting['account'], 'onblur="updateEo(this);"', false, false, array(), true);
+}
+echo $accountWidgets[$a];
+echo $overflow_end;
+?>
+  </td>   
+  <td class="ohide">
+   <?php echo $overflow_begin ?><input name="desc[]" type="text" value="<?php echo $posting['desc'] ?>" size="50" maxlength="250" /><?php echo $overflow_end ?>
+  </td>
+  <td>
+   <input name="amount[]" type="text" value="<?php echo $posting['posting_id'] === null ? '' : Fima::convertValueToAmount($posting['amount']) ?>" onfocus="updateAssetResult(this);" onblur="formatAmount(this); updateResult(); updateAssetResult(this);" size="12" maxlength="255" class="amount" />
+  </td>
+  <td align="center">
+   <input name="eo[<?php echo $rowId-1 ?>]" type="hidden" value="0" />
+   <input name="eo[<?php echo $rowId-1 ?>]" id="eo<?php echo $rowId-1 ?>" type="checkbox" value="1" <?php if ($posting['eo']) { echo ' checked="checked"'; } ?> />
+  </td>
+ </tr>
+<?php endforeach; ?>
+ <tr class="postingInfo">
+  <td colspan="4" class="rightAlign"><span id="infoasset"></span> <?php echo _("Result") ?>:</td>
+  <td><span id="infoassetresult" class="positive postingResult"></span></div>
+  <td></td>
+ </tr>
+ <tr class="postingInfo">
+  <td colspan="4" class="rightAlign"><?php echo _("Current Result") ?>:</td>
+  <td><span id="inforesult" class="positive postingResult"></span></div>
+  <td></td>
+ </tr>
diff --git a/fima/templates/postings/empty.inc b/fima/templates/postings/empty.inc
new file mode 100644 (file)
index 0000000..d0105f3
--- /dev/null
@@ -0,0 +1,5 @@
+<tr>
+ <td class="text" colspan="6">
+  &nbsp;<em><?php echo _("There are no appropriate postings.") ?></em>
+ </td>
+</tr>
diff --git a/fima/templates/postings/footer.inc b/fima/templates/postings/footer.inc
new file mode 100644 (file)
index 0000000..85cd419
--- /dev/null
@@ -0,0 +1,5 @@
+<?php if ($pageOb['mode'] != 'list'): ?>
+<input type="hidden" name="actionID" value="<?php echo $actionID ?>" />
+</form>
+<?php endif; ?>
+<br />
diff --git a/fima/templates/postings/header.inc b/fima/templates/postings/header.inc
new file mode 100644 (file)
index 0000000..612177b
--- /dev/null
@@ -0,0 +1,39 @@
+<div class="header">
+ <div <?php if ($pageOb['page_count'] > 1) { echo 'style="width:40%" '; } ?> class="leftFloat">
+  <span dir="ltr"><?php echo htmlspecialchars($title) ?></span>
+<?php if ($pageOb['mode'] == 'list'): ?>
+  <?php echo Horde::link($pageOb['url'], _("Refresh List")) . Horde::img('reload.png', _("Refresh List"), '', $registry->getImageDir('horde')) ?> </a>
+<?php if (!isset($_SESSION['fima_search'])): ?>
+  <?php echo Horde::link(Util::addParameter(Horde::applicationUrl('search.php'), 'search_type', $prefs->getValue('active_postingtype')), _("Search Postings")) . Horde::img('search.png', _("Search"), '', $registry->getImageDir('horde')) ?></a>
+<?php else: ?>
+  <?php echo Horde::link(Horde::applicationUrl('search.php'), _("Edit Search Query")) . Horde::img('edit.png', _("Edit Search Query"), '', $registry->getImageDir('horde')) ?></a>
+  <?php echo Horde::link(Util::addParameter($pageOb['url'], 'actionID', 'clear_search'), _("Clear Search Query")) . Horde::img('delete.png', _("Clear Search Query"), '', $registry->getImageDir('horde')) ?></a>
+<?php endif; ?>
+<?php endif; ?>
+ </div>
+  
+<?php if ($pageOb['mode'] == 'list'): ?>
+<?php if ($pageOb['page_count'] > 1): ?>
+ <div style="width:20%" class="leftFloat">
+  <?php printf(_("Page %d of %d"), $pageOb['page'], $pageOb['page_count']) ?>
+ </div>
+ <div style="width:40%" class="rightFloat rightAlign">
+<?php else: ?>
+ <div class="rightFloat rightAlign">
+<?php endif; ?>
+  <?php echo $pageOb['postings_count'] ?>
+ </div>
+<?php endif; ?>
+<?php if ( $GLOBALS['browser']->isBrowser('msie') && $GLOBALS['browser']->getMajor() < 7): ?>
+ <div class="clear" style="clear:none" /></div>
+<?php else: ?>
+ <div class="clear"></div>
+<?php endif; ?>
+</div>
+
+<?php if ($pageOb['mode'] != 'list'): ?>
+<form method="post" name="postings" action="<?php echo $pageOb['url'] ?>">
+<?php Util::pformInput() ?>
+<?php endif; ?>
+
diff --git a/fima/templates/postings/javascript_edit.inc b/fima/templates/postings/javascript_edit.inc
new file mode 100644 (file)
index 0000000..ed2d77b
--- /dev/null
@@ -0,0 +1,357 @@
+<script type="text/javascript">
+<!--
+
+function updateWday(field)
+{
+<?php if ($GLOBALS['browser']->hasFeature('dom')): ?>
+    wdays = new Array(
+        '<?php echo addslashes(_("Sunday")) ?>',
+        '<?php echo addslashes(_("Monday")) ?>',
+        '<?php echo addslashes(_("Tuesday")) ?>',
+        '<?php echo addslashes(_("Wednesday")) ?>',
+        '<?php echo addslashes(_("Thursday")) ?>',
+        '<?php echo addslashes(_("Friday")) ?>',
+        '<?php echo addslashes(_("Saturday")) ?>'
+    );
+    spanObj = _get(field + '_wday');
+    day = _get(field + '[day]').value;
+    month = _get(field + '[month]').value - 1;
+    year = _get(field + '[year]').value;
+
+    date = new Date(year, month, day)
+    spanObj.innerHTML = '(' + wdays[date.getDay()] + ')';
+<?php endif; ?>
+}
+
+function _get(id)
+{
+    return document.getElementById ?
+        document.getElementById(id) :
+        eval('document.all.' + id);
+}
+
+function _getall(fieldname)
+{
+    return document.getElementsByName ?
+        document.getElementsByName(fieldname) :
+        eval('document.all.' + fieldname);
+}
+
+var amountformat = '<?php echo addslashes($amountfmt) ?>';
+var hsign = amountformat.substr(0, 1);
+var comma = amountformat.substr(1, 1);
+
+function convertAmountToValue(amount)
+{
+    hsignx = (hsign == '.') ? '\\.' : hsign;
+    commax = (comma == '.') ? '\\.' : comma;
+    if (hsign != '.' && comma != '.') {
+        amount = amount.replace(/\./g, '');
+    }
+    amount = amount.replace(new RegExp(hsignx, 'g'), '');
+    amount = amount.replace(new RegExp(commax, 'g'), '.');
+
+    try {
+        amounteval = eval(amount);
+        amount = amounteval;
+    } catch (e) {
+    }
+
+    val = parseFloat(amount);
+    if (isNaN(val)) {
+        return 0;
+    }
+    
+    return val;
+}
+
+function convertValueToAmount(val)
+{
+    val = val.toString();
+    if (val.indexOf('.') == -1) {
+        parts = new Array(val, '00');
+    } else {
+        parts = val.split('.');
+        parts[1] += '00';
+    }
+
+    intpart = '';
+    i = parts[0].length;
+    while (i > 0) {
+        if (i - 3 < 0) {
+            d = i;
+            i = 0;
+        } else {
+            d = 3;
+            i = i - 3;
+        }
+        part = parts[0].substr(i, d);
+        if (part == '-' || intpart == '') {
+            intpart = part + intpart;
+        } else {
+            intpart = part + hsign + intpart;
+        }
+    }    
+    fracpart = parts[1].substr(0, 2);
+
+    return intpart + comma + fracpart;
+}
+
+function formatAmount(field)
+{
+    if (field.value != '') {
+        val = convertAmountToValue(field.value);
+        field.value = convertValueToAmount(val);
+    }
+}
+
+
+
+var dateformat = '<?php echo addslashes($datefmt) ?>';
+var separator = dateformat.match(/[^%a-zA-Z]/);
+var datepos = new Array();
+
+function convertDateToStamp(datevalue)
+{
+    if (separator == '') {
+        return;
+    }
+    
+    formatparts = dateformat.split(separator);
+    
+    datevalue = datevalue.replace(/[^0-9]/g, separator);
+    dateparts = datevalue.split(separator);
+
+    datepos = new Array();
+    for (i = 0, j = 0, p = 0; i < formatparts.length; i++, j++) {
+        if (formatparts[i] == '%Y')        { d = 0;
+        } else if (formatparts[i] == '%m') { d = 1;
+        } else if (formatparts[i] == '%d') { d = 2;
+        } else { continue; }
+        
+        datepos[d] = i;
+        
+        if (datevalue.indexOf(separator) == -1) {
+          if (d == 0 && datevalue.length <= 4) {
+              dateparts[i] = parseInt((new Date()).getFullYear(), 10);
+          } else {
+              dp = (d == 0 && datevalue.length > 6) ? 4 : 2;
+              dateparts[i] = parseInt(datevalue.substr(p, dp), 10);
+              p += dp;
+          }
+        } else if (d == 0 && dateparts.length < 3) {
+          dateparts[i] = parseInt((new Date()).getFullYear(), 10);
+          j--;
+        } else if (dateparts[j]) {
+            dateparts[i] = parseInt(dateparts[j], 10);
+        } else {
+            dateparts[i] = 1;
+        }
+    }
+
+    if (dateparts[datepos[0]] < 70) {
+        dateparts[datepos[0]] += 2000;
+    } else if (dateparts[datepos[0]] < 100) {
+        dateparts[datepos[0]] += 1900;
+    }
+
+    stamp = new Date(dateparts[datepos[0]], dateparts[datepos[1]] - 1, dateparts[datepos[2]]);
+    return stamp;
+}
+
+function convertStampToDate(stamp)
+{
+    dateparts = new Array();
+    dateparts[datepos[0]] = stamp.getFullYear().toString();
+    dateparts[datepos[1]] = (101 + stamp.getMonth()).toString().substr(1, 2);
+    dateparts[datepos[2]] = (100 + stamp.getDate()).toString().substr(1, 2);
+
+    return dateparts.join(separator);
+}
+
+function formatDate(field)
+{
+    if (field.value == '') {
+        return;
+    }
+     
+    stamp = convertDateToStamp(field.value);
+    field.value = convertStampToDate(stamp);
+}
+
+function updateDate(field)
+{
+    if (field.value != '') {
+        return;
+    }
+
+    if (field.value == '') {
+        fields = _getall(field.name);
+        for (i = 0; i < fields.length; i++) {
+            if (fields[i] == field) {
+                if (i > 0) {
+                    field.value = fields[i - 1].value;
+                }
+                break;
+            }
+        }
+    }
+    return;
+}
+
+function updateAccount(field)
+{
+    if (!field.options) {
+        return;
+    }
+    
+    if (field.options[field.selectedIndex].value == '') {
+        if (field.length == 2) {
+            field.options[field.length - 1].selected = true;
+        } else {
+            fields = _getall(field.name);
+            for (i = 0; i < fields.length; i++) {
+                if (fields[i] == field) {
+                    if (i > 0) {
+                        field.selectedIndex = fields[i - 1].selectedIndex + (fields[i - 1].options[0].value == '' ? 0 : 1);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+}
+
+function updateEo(field)
+{
+    if (!field.options) {
+        return;
+    }
+    
+    eo = (field.options[field.selectedIndex].className.indexOf('eo') != -1);
+    accountfields = _getall('account[]');
+
+    for (i = 0; i < accountfields.length; i++) {
+        if (accountfields[i] == field) {
+            _get('eo' + i).checked = eo;
+            break;
+        }
+    }
+}
+
+function updateResult()
+{
+<?php if ($GLOBALS['browser']->hasFeature('dom')): ?>
+    result = 0;
+    amountfields = _getall('amount[]');
+    accountfields = _getall('account[]');
+    for (i = 0; i < amountfields.length; i++) {
+        if (accountfields[i].options[accountfields[i].selectedIndex].className.indexOf('<?php echo FIMA_ACCOUNTTYPE_ASSET ?>') == -1) {
+            result += convertAmountToValue(amountfields[i].value) * (<?php if (!$prefs->getValue('expenses_sign')) { ?>(accountfields[i].options[accountfields[i].selectedIndex].className.indexOf('<?php echo FIMA_ACCOUNTTYPE_EXPENSE ?>') != -1) ? -1 : <?php } ?>1);
+        }
+    }
+    
+    spanObj = _get('inforesult');
+    spanObj.innerHTML = convertValueToAmount(result);
+    spanObj.className = ((result >= 0) ? 'positive' : 'negative') + ' postingResult';
+<?php endif; ?>
+}
+
+var AssetIndex;
+var AssetResult;
+
+function updateAssetResult(field)
+{
+<?php if ($GLOBALS['browser']->hasFeature('dom')): ?>
+    amountfields = _getall('amount[]');
+    assetfields = _getall('asset[]');
+    accountfields = _getall('account[]');
+    
+    for (i = 0; i < amountfields.length; i++) {
+        if (amountfields[i] == field || assetfields[i] == field) {
+            break;
+        }
+    }
+
+    asset = assetfields[i].options[assetfields[i].selectedIndex];    
+    if (asset.value == '') {
+        assettext = '';
+        result = 0;
+    } else {
+        assettext = asset.text;
+        result = 0;
+        for (i = 0; i < AssetIndex.length; i++) {
+            if (AssetIndex[i] == asset.value) {
+                ar = parseFloat(AssetResult[i]);
+                if (!isNaN(ar)) {
+                    result += ar;
+                }
+                break;
+            }
+        }        
+        for (i = 0; i < amountfields.length; i++) {
+            if (assetfields[i].options[assetfields[i].selectedIndex].value == asset.value) {
+                result += convertAmountToValue(amountfields[i].value) * (<?php if (!$prefs->getValue('expenses_sign')) { ?>(accountfields[i].options[accountfields[i].selectedIndex].className.indexOf('<?php echo FIMA_ACCOUNTTYPE_EXPENSE ?>') != -1) ? -1 : <?php } ?>1);
+            }
+            if (accountfields[i].options[accountfields[i].selectedIndex].value == asset.value) {
+                result -= convertAmountToValue(amountfields[i].value);
+            }
+        }
+    }
+
+    spanObj = _get('infoasset');
+    spanObj.innerHTML = assettext;
+        
+    spanObj = _get('infoassetresult');
+    spanObj.innerHTML = convertValueToAmount(result);
+    spanObj.className = ((result >= 0) ? 'positive' : 'negative') + ' postingResult';
+<?php endif; ?>
+}
+
+function selectAccounts(field, type, active)
+{
+    accounts = _get(field);
+    for (i = 0; i < accounts.length; i++) {
+        if (accounts.options[i].className.indexOf(type) != -1) {
+            accounts.options[i].selected = active;
+        }
+    }
+}
+
+function autofillPostings() {
+    datefields = _getall('date[]');
+    assetfields = _getall('asset[]');
+    accountfields = _getall('account[]');
+    descfields = _getall('desc[]');
+    amountfields = _getall('amount[]');
+
+    for (j = 0; j < 12; j++) {
+        if (!datefields[j]) {
+            break;
+        }
+
+        if (j == 0) {
+            year = convertDateToStamp(datefields[j].value).getFullYear().toString();
+        } else {
+            assetfields[j].selectedIndex = assetfields[j - 1].selectedIndex + (assetfields[j - 1].options[0].value == '' ? 0 : 1);
+            accountfields[j].selectedIndex = accountfields[j - 1].selectedIndex + (accountfields[j - 1].options[0].value == '' ? 0 : 1);
+            if (descfields[j].value == '') {
+                descfields[j].value = descfields[j - 1].value;
+            }
+            if (amountfields[j].value == '') {
+                amountfields[j].value = amountfields[j - 1].value;
+            }
+        }
+        datefields[j].value = convertStampToDate(new Date(year, j, 1));
+    }
+    
+    updateResult();
+    updateAssetResult(assetfields[0]);
+}
+
+function Submit(action) {
+    return true;
+}
+
+// -->
+</script>
diff --git a/fima/templates/postings/javascript_list.inc b/fima/templates/postings/javascript_list.inc
new file mode 100644 (file)
index 0000000..eaf7a70
--- /dev/null
@@ -0,0 +1,165 @@
+<script type="text/javascript">
+<!--
+
+function AnySelected()
+{
+    for (i = 0; i < document.postings.elements.length; i++) {
+        if (document.postings.elements[i].checked) return true;
+    }
+    return false;
+}
+
+var rowColors = new Array();
+function selectRow(rowId)
+{
+    rowOb = document.getElementById('row' + rowId);
+    if (!rowOb) {
+        return;
+    }
+
+    if (rowOb.className.indexOf('selectedRow') != -1) {
+        rowOb.className = rowColors[rowId];
+    } else {
+        rowColors[rowId] = rowOb.className;
+        rowOb.className = 'selectedRow';
+    }
+}
+
+function getIndexByElement(elem)
+{
+    for (var i = 0; i < elem.form.elements.length; i++) {
+        if (elem == elem.form.elements[i]) return i;
+    }
+    return null;
+}
+
+function Submit(action)
+{
+    if (!AnySelected() && action != 'transfer_postings') {
+        window.alert('<?php echo addslashes(_("You must select at least one posting first.")) ?>');
+        return;
+    }
+
+<?php if ($prefs->getValue('delete_opt') == 1): ?>
+    if (action == 'delete_postings') {
+        if (!window.confirm('<?php echo addslashes(_("Are you sure you wish to PERMANENTLY delete these postings?")) ?>')) {
+            return;
+        }
+    }
+<?php endif; ?>
+    document.postings.actionID.value = action;
+    document.postings.submit();
+}
+
+function makeSelection(whichForm)
+{
+    switch (parseInt(whichForm)) {
+    case -1:
+        if (document.postings.checkAll.checked) {
+            flag = "!0";
+        } else {
+            flag = "0";
+        }
+        break;
+    case 1:
+        flag = document.select1.filter.options[document.select1.filter.selectedIndex].value;
+        break;
+    default:
+        flag = document.select2.filter.options[document.select2.filter.selectedIndex].value;
+    }
+
+    if (flag.substring(0, 1) == "!") {
+        selectFlaged(flag.substring(1), false);
+    } else if (flag.substring(0, 1) == "~") {
+        selectFlaged(flag.substring(0,1), null);
+    } else {
+        selectFlaged(flag, true);
+    }
+
+    // Reset the form.
+    switch (parseInt(whichForm)) {
+    case -1:
+        break;
+
+    case 1:
+        document.select1.reset();
+        break;
+
+    default:
+        document.select2.reset();
+    }
+}
+
+var startrange = -1;
+
+function selectRange(e)
+{
+    var checkBox = (e.srcElement || e.target);
+    var endrange = getIndexByElement(checkBox);
+
+    if (startrange >= 0 && e.shiftKey) {
+        if (document.postings.elements[startrange].checked == document.postings.elements[endrange].checked) {
+            if (startrange > endrange) {
+                var tmp = endrange;
+                endrange = startrange;
+                startrange = tmp;
+            }
+            for (var i = startrange + 1; i < endrange; i++) {
+                document.postings.elements[i].checked = document.postings.elements[startrange].checked;
+                selectRow(document.postings.elements[i].id.replace(/check/, ''));
+            }
+        }
+        startrange = -1;
+    } else {
+        startrange = getIndexByElement(checkBox);
+    }
+}
+
+function onClickHandler(e)
+{
+    var e = e || window.event;
+    var elem = (e.srcElement || e.target);
+
+    // Range selection/deselection.
+    if (elem.name == "indices[]") {
+        selectRange(e);
+    }
+}
+
+document.onclick = onClickHandler;
+
+var Flags;
+
+function selectFlaged(flag, val)
+{
+    shift = 0;
+    for (var i = 0; i < document.postings.elements.length; i++) {
+        while (document.postings.elements[i].name != "indices[]") {
+            i++;
+            shift++;
+            if (!document.postings.elements[i]) {
+                return;
+            }
+        }
+        
+        if (document.postings.elements[i].disabled) {
+            continue;
+        }
+
+        if (flag == '~') {
+            check = !document.postings.elements[i].checked;
+        } else if (flag & Flags[i - shift]) {
+            check = val;
+        } else {
+            check = !val;
+        }
+
+        if (document.postings.elements[i].checked != check) {
+            document.postings.elements[i].checked = check;
+            selectRow(document.postings.elements[i].id.replace(/check/, ''));
+        }
+    }
+}
+
+//-->
+</script>
diff --git a/fima/templates/postings/javascript_shift.inc b/fima/templates/postings/javascript_shift.inc
new file mode 100644 (file)
index 0000000..17c33ce
--- /dev/null
@@ -0,0 +1,9 @@
+<script type="text/javascript">
+<!--
+
+function Submit(action) {
+    return true;
+}
+
+// -->
+</script>
diff --git a/fima/templates/postings/javascript_transfer.inc b/fima/templates/postings/javascript_transfer.inc
new file mode 100644 (file)
index 0000000..f98af39
--- /dev/null
@@ -0,0 +1,13 @@
+<script type="text/javascript">
+<!--
+
+function Submit(action) {
+    if (document.postings.type_from.options[document.postings.type_from.selectedIndex].value == document.postings.type_to.options[document.postings.type_to.selectedIndex].value) {
+        alert("<?php echo _("Please select another posting type where to transfer postings to.") ?>");
+        return false;
+    }
+    return true;
+}
+
+// -->
+</script>
diff --git a/fima/templates/postings/list.inc b/fima/templates/postings/list.inc
new file mode 100644 (file)
index 0000000..ca145dd
--- /dev/null
@@ -0,0 +1,15 @@
+<?php foreach($postings as $postingId => $posting): ?>
+<?php $rowId++; ?>
+ <tr id="row<?php echo $rowId ?>" class="<?php if (!$print_view) { echo ($posting['eo'] ? 'eo' : '') . $posting['account_type']; } ?>">
+  <td><?php if (!$print_view) { ?><input id="check<?php echo $rowId ?>" type="checkbox" class="checkbox" name="indices[]" onclick="selectRow(<?php echo $rowId ?>);" value="<?php echo $postingId ?>"<?php if ($closedperiod >= $posting['date']) echo ' disabled="disabled"' ?> /><?php } echo Horde::img(($posting['eo'] ? 'eo' : '') . $posting['account_type'] . '.png', $posting['account_type_eo'], array('title' => $posting['account_type_eo'])) ?></td>
+  <td class="ohide"><?php echo $overflow_begin, strftime($datefmt, $posting['date']), $overflow_end ?></td>
+  <td class="ohide <?php echo $posting['asset_closed'] ? 'closed' : ''?>" title="<?php echo strlen($posting['asset_label']) > 25 ? $posting['asset_label'] : '' ?>"><?php echo $overflow_begin, $posting['asset_label'], $overflow_end ?></td>
+  <td class="ohide <?php echo $posting['account_closed'] ? 'closed' : ''?>" title="<?php echo strlen($posting['account_label']) > 25 ? $posting['account_label'] : '' ?>"><?php echo $overflow_begin, $posting['account_label'], $overflow_end ?></td>
+  <td class="ohide" title="<?php echo strlen($posting['desc']) > 25 ? $posting['desc'] : '' ?>"><?php echo $overflow_begin, $posting['desc'], $overflow_end ?></td>
+  <td class="<?php echo ($posting['amount'] < 0) ? 'negative' : 'positive' ?> amount"><?php echo $overflow_begin, Fima::convertValueToAmount($posting['amount']), $overflow_end ?></td>
+ </tr>
+<?php endforeach; ?>
+ <tr class="postingInfo">
+  <td colspan="5" class="rightAlign"><?php echo _("Total Result") ?>:</td>
+  <td class="<?php echo (Fima::getPostingsResult() < 0) ? 'negative' : 'positive' ?> amount"><?php echo Fima::convertValueToAmount(Fima::getPostingsResult()) ?></td>
+ </tr>
diff --git a/fima/templates/postings/navbar.inc b/fima/templates/postings/navbar.inc
new file mode 100644 (file)
index 0000000..41e1afc
--- /dev/null
@@ -0,0 +1,33 @@
+<?php if ($pageOb['mode'] != 'list') { return; } ?>
+<table width="100%" cellspacing="0">
+ <tr class="mboxcontrol">
+  <td>
+   <div class="leftFloat">
+    <form name="select<?php echo $form ?>" action="javascript:void(0)" onsubmit="return false">
+    <select name="filter" onchange="makeSelection(<?php echo $form ?>);">
+     <option value="" selected="selected"><?php echo _("Select") ?>:</option>
+     <option value="!0"><?php echo _("All") ?></option>
+     <option value="0"><?php echo _("None") ?></option>
+     <option value="~0"><?php echo _("Invert") ?></option>
+<?php $flagpos = 0; ?>
+<?php foreach ($accounttypes as $typeId => $typeLabel): ?>
+     <option value="<?php echo pow(2, $flagpos) ?>"><?php echo $typeLabel ?></option>
+     <option value="!<?php echo pow(2, $flagpos++) ?>"><?php echo sprintf(_("Not %s"), $typeLabel) ?></option>
+<?php endforeach; ?>
+    </select>
+    </form>
+   </div>
+
+   <div class="rightFloat">
+<?php if ($pageOb['page_count'] > 1): ?>
+    <form method="get" name="pagenav" action="<?php echo Horde::applicationUrl('postings.php') ?>">
+    <?php Util::pformInput() ?>
+    <?php echo $pageOb['pages_first'] . '&nbsp;' . $pageOb['pages_prev'] ?>
+    <input type="text" name="page" value="<?php echo htmlspecialchars($pageOb['page']) ?>" size="<?php echo String::length($pageOb['page_count']) ?>" />
+    <?php echo $pageOb['pages_next'] . '&nbsp;' . $pageOb['pages_last'] ?>
+    </form>
+<?php endif; ?>
+   </div>
+  </td>
+ </tr>
+</table>
diff --git a/fima/templates/postings/posting_footers.inc b/fima/templates/postings/posting_footers.inc
new file mode 100644 (file)
index 0000000..f8448b6
--- /dev/null
@@ -0,0 +1,56 @@
+</table>
+<?php if ($pageOb['mode'] == 'list'): ?>
+<input type="hidden" name="page" value="<?php echo $pageOb['page'] ?>" />
+<input type="hidden" name="actionID" value="" />
+</form>
+
+<script type="text/javascript">
+<!--
+
+Flags = new Array(<?php
+if (isset($flags) && is_array($flags)) {
+    for ($i = 0; $i < count($flags); $i++) {
+        if ($i > 0) {
+            echo ', ';
+        }
+        echo '"' . $flags[$i] . '"';
+    }
+}
+?>);
+
+//-->
+</script>
+<?php elseif($pageOb['mode'] == 'edit'): ?>
+<script type="text/javascript">
+<!--
+
+<?php
+$assetindex = '';
+$assetresult = '';
+if (isset($assetresults) && is_array($assetresults)) {
+    $now = mktime();
+    $currentassets = array();
+    foreach ($postings as $postingId => $posting) {
+        if ($posting['asset'] && $posting['date'] <= $now) {
+            $currentassets[$posting['asset']] = $posting['amount'];
+        }
+    }
+    foreach ($assetresults as $key => $asset) {
+        if (isset($currentassets[$asset['account_id']])) {
+            $asset['account_result'] -= $currentassets[$asset['account_id']];
+        }
+        if ($key > 0) {
+            $assetindex .= ', ';
+            $assetresult .= ', ';
+        }
+        $assetindex .= '"' . $asset['account_id'] . '"';
+        $assetresult .= '"' . (float)$asset['account_result'] . '"';
+    }
+}
+?>
+AssetIndex = new Array(<?php echo $assetindex ?>);
+AssetResult = new Array(<?php echo $assetresult ?>);
+
+//-->
+</script>
+<?php endif; ?>
diff --git a/fima/templates/postings/posting_headers.inc b/fima/templates/postings/posting_headers.inc
new file mode 100644 (file)
index 0000000..ce9379d
--- /dev/null
@@ -0,0 +1,82 @@
+<?php if ($pageOb['mode'] == 'shift' || $pageOb['mode'] == 'transfer'): ?>
+<tr class="control"><td colspan="6">
+<table cellspacing="0" width="100%" class="linedRow">
+<?php return; endif; ?>
+<?php
+if (!isset($headers_inc_count)) {
+    $headers_inc_count = 0;
+} else {
+    $headers_inc_count++;
+}
+$sortImg = ($sortdir) ? 'za.png' : 'az.png';
+$sortText = ($sortdir) ? '\/' : '/\\';
+$headers = array(
+    FIMA_SORT_DATE => array(
+        'stext' => _("Sort by Date"),
+        'text' => _("Da_te"),
+        'width' => '90'
+    ),
+    FIMA_SORT_ASSET => array(
+        'stext' => _("Sort by Asset Account"),
+        'text' => _("A_sset Account"),
+        'width' => '33%'
+    ),
+    FIMA_SORT_ACCOUNT => array(
+        'stext' => _("Sort by Posting Account"),
+        'text' => _("Posting A_ccount"),
+        'width' => '33%'
+    ),
+    FIMA_SORT_DESC => array(
+        'stext' => _("Sort by Description"),
+        'text' => _("Descriptio_n"),
+        'width' => '33%'
+    ),
+    FIMA_SORT_AMOUNT => array(
+        'stext' => _("Sort by Amount"),
+        'text' => _("Amo_unt"),
+        'width' => '100'
+    )
+);
+
+if ($pageOb['mode'] == 'edit') {
+    $headers[] = array(
+        'text' => _("e.o."),
+        'width' => '25'
+    );
+}
+
+?>
+<?php if ($pageOb['mode'] == 'list'): ?>
+<form method="post" name="postings" action="<?php echo $pageOb['url'] ?>">
+<?php Util::pformInput() ?>
+<?php endif; ?>
+<table class="postingList" width="100%" cellspacing="0">
+ <tr class="item">
+<?php if ($pageOb['mode'] == 'list'): ?>
+<?php if (!$headers_inc_count): ?>
+  <th id="checkheader" width="35" onclick="document.messages.checkAll.checked = !document.messages.checkAll.checked; makeSelection(-1);">
+<?php if (!$print_view): ?>  
+   <label for="checkAll" class="hidden">_("Check _All/None")</label>
+   <input type="checkbox" class="checkbox" id="checkAll" name="checkAll" onclick="makeSelection(-1);" <?php echo Horde::getAccessKeyAndTitle(_("Check _All/None")) ?> />
+<?php endif; ?>
+  </th>
+<?php else: ?>
+  <th>&nbsp;</th>
+<?php endif; ?>
+<?php endif; ?>
+
+<?php foreach ($headers as $key => $val): ?>
+<?php if ($pageOb['mode'] == 'list'): ?>
+  <th class="<?php echo ($sortby == $key) ? 'selected' : 'item' ?>" width="<?php echo $val['width'] ?>" onclick="document.location.href='<?php echo addslashes(Util::addParameter(($sortby == $key) ? $sorturl : $pageOb['url'], array('sortby' => $key, 'actionID' => 'change_sort'))) ?>';">
+  <?php if ($sortby == $key): ?>
+    <?php echo Horde::link(Util::addParameter($sorturl, array('sortby' => $key, 'actionID' => 'change_sort')), $val['stext'], null, null, null, $val['stext']) . Horde::img($sortImg, $sortText, '', $registry->getImageDir('horde')) ?></a>
+  <?php endif; ?>
+  <?php echo Horde::widget(Util::addParameter(($sortby == $key) ? $sorturl : $pageOb['url'], array('sortby' => $key, 'actionID' => 'change_sort')), $val['stext'], 'widget', null, null, $val['text']) ?>
+<?php else: ?>
+  <th class="item" width="<?php echo $val['width'] ?>">
+  <?php echo str_replace('_', '', $val['text']) ?>
+<?php endif; ?>
+<?php if (isset($val['extra'])) echo $val['extra']; ?>
+  </th>
+<?php endforeach; ?>
+ </tr>
diff --git a/fima/templates/postings/shift.inc b/fima/templates/postings/shift.inc
new file mode 100644 (file)
index 0000000..b4e987a
--- /dev/null
@@ -0,0 +1,34 @@
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo _("Shifting") ?>&nbsp;&nbsp;</strong>
+  </td>
+  <td width="100%"><?php echo sprintf(_("%d selected Postings"), count($postings)) ?>
+<?php foreach($postings as $postingId => $posting): ?>
+    <input name="posting_id[]" type="hidden" value="<?php echo $posting['posting_id'] ?>" />
+<?php endforeach; ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('type', _("Posting T_ype")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildPostingTypeWidget('type', null, '', _("don't change"), false) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('asset', _("A_sset Account")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildAccountWidget('asset', null, '', _("don't change"), false, array(array('type', FIMA_ACCOUNTTYPE_ASSET))) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('account', _("Posting A_ccount")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildAccountWidget('account', null, '', _("don't change")) ?>
+  </td>
+ </tr>
diff --git a/fima/templates/postings/transfer.inc b/fima/templates/postings/transfer.inc
new file mode 100644 (file)
index 0000000..1da028e
--- /dev/null
@@ -0,0 +1,64 @@
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('type_from', _("Transfer from")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildPostingTypeWidget('type_from', $prefs->getValue('active_postingtype'), ''); ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('period_from', _("Period from")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildDateWidget('period_from', 0, null, _("All"), true) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('keep', _("_Keep original Postings")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <input name="keep" type="hidden" value="0" />
+   <input name="keep" type="checkbox" value="1" id="keep" checked="checked" />
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap" valign="top">
+   <strong><?php echo Horde::label('summarize', _("_Summarize Accounts")) ?></strong>&nbsp;
+  </td>
+  <td valign="top" width="100%">
+   <input id="summarize_none" name="summarize" type="radio" value="none" checked="checked" />
+   <?php echo Horde::label('summarize_none', _("Don't summarize.")) ?><br />
+   <input id="summarize_combine" name="summarize" type="radio" value="combine" />
+   <?php echo Horde::label('summarize_combine', _("Summarize by combining.")) ?><br />
+   <input id="summarize_post" name="summarize" type="radio" value="post" />
+   <?php echo Horde::label('summarize_post', _("Summarize by posting against")) ?><br />
+   <?php echo Fima::buildAccountWidget('summarize_post_account', null, 'onchange="document.postings.summarize[2].checked = true;"', false, false, array(array('type', FIMA_ACCOUNTTYPE_ASSET))) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('type_to', _("Transfer to")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildPostingTypeWidget('type_to', null); ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('period_to', _("Period to")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <?php echo Fima::buildDateWidget('period_to', 0, null, _("All"), true) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('delete', _("_Delete existing Postings")) ?></strong>&nbsp;
+  </td>
+  <td width="100%">
+   <input name="delete" type="hidden" value="0" />
+   <input name="delete" type="checkbox" value="1" />
+  </td>
+ </tr>
diff --git a/fima/templates/prefs/closedperiodselect.inc b/fima/templates/prefs/closedperiodselect.inc
new file mode 100644 (file)
index 0000000..65a7451
--- /dev/null
@@ -0,0 +1,6 @@
+<?php if (!$prefs->isLocked('closed_period')): ?>
+<br />
+<?php echo _("Closed by period:") ?><br />
+<?php echo Fima::buildDateWidget('closedperiod', (int)$prefs->getValue('closed_period'), '', _("None"), true); ?>
+</select><br /><br />
+<?php endif; ?>
\ No newline at end of file
diff --git a/fima/templates/prefs/ledgerselect.inc b/fima/templates/prefs/ledgerselect.inc
new file mode 100644 (file)
index 0000000..392890b
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+if (!$prefs->isLocked('activet_ledger')):
+    $ledgers = Fima::listLedgers();
+    if (($active_ledger = $prefs->getValue('active_ledger')) == null ||
+        !isset($ledgers[$active_ledger])) {
+        $active_ledger = Auth::getAuth();
+    }
+?>
+
+<?php echo _("Your active ledger:") ?><br />
+<select name="active_ledger">
+<?php foreach ($ledgers as $id => $ledger): ?>
+    <option value="<?php echo htmlspecialchars($id) ?>"<?php if ($id == $active_ledger) echo ' selected="selected"' ?>><?php echo htmlspecialchars($ledger->get('name')) ?></option>
+<?php endforeach; ?>
+</select><br /><br />
+<?php endif; ?>
diff --git a/fima/templates/reports/empty.inc b/fima/templates/reports/empty.inc
new file mode 100644 (file)
index 0000000..65281ac
--- /dev/null
@@ -0,0 +1,2 @@
+<br />
+<p><?php echo _("No data for this report.") ?></p>
diff --git a/fima/templates/reports/graph.inc b/fima/templates/reports/graph.inc
new file mode 100644 (file)
index 0000000..7ddbab2
--- /dev/null
@@ -0,0 +1,3 @@
+<br />
+<p><img class="reportImage" src="<?php echo Util::addParameter($report->getParam('url'), 'out', '1') ?>" alt="<?php echo _("Report") ?>" title="<?php echo htmlspecialchars($title) ?>" /></p>
+<?php /*<--<p><iframe src="<?php echo Util::addParameter($report->getParam('url'), 'out', '1') ?>" /></iframe></p>-->*/ ?>
diff --git a/fima/templates/reports/img.inc b/fima/templates/reports/img.inc
new file mode 100644 (file)
index 0000000..6d33aa9
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+if (!isset($report)) {
+return;
+}
+
+$report->getGraph();
diff --git a/fima/templates/reports/reports.inc b/fima/templates/reports/reports.inc
new file mode 100644 (file)
index 0000000..4eba986
--- /dev/null
@@ -0,0 +1,107 @@
+<form method="post" name="report" action="report.php">
+<?php Util::pformInput() ?>
+<input type="hidden" name="actionID" value="open_report" />
+<input type="hidden" name="sortby" value="<?php echo $params['sortby'] ?>" />
+<input type="hidden" name="sortdir" value="<?php echo $params['sortdir'] ?>" />
+
+<h1 class="header"><?php echo htmlspecialchars($title) ?></h1>
+
+<table cellspacing="0" width="100%" class="linedRow">
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('report_id', _("Repor_t")) ?></strong>
+  </td>
+  <td width="100%" colspan="4">
+   <select name="report_id" id="report_id">
+<?php foreach($_reports as $reportId => $reportLabel): ?>
+    <option value="<?php echo $reportId ?>" <?php if ($reportId == $params['report_id']) { echo 'selected="selected"'; } ?>><?php echo $reportLabel; ?></option>
+<?php endforeach; ?>
+   </select>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('type', _("_Display")) ?></strong>
+  </td>
+  <td>
+   <select name="display" id="display">
+<?php foreach($displays as $displayId => $displayLabel): ?>
+    <option value="<?php echo $displayId ?>" <?php if ($displayId == $params['display']) { echo 'selected="selected"'; } ?>><?php echo $displayLabel; ?></option>
+<?php endforeach; ?>
+   </select>
+  </td>
+  <td class="rightAlign nowrap" rowspan="4" valign="top">
+   <strong><?php echo Horde::label('posting_account[]', _("Accounts")) ?></strong>  
+  </td>
+  <td class="nowrap" rowspan="4">
+   <?php echo Fima::buildAccountWidget('posting_account[]', $params['posting_account'], '', false, true, array(array('type', FIMA_ACCOUNTTYPE_ASSET, '<>'))) ?>
+  </td>
+  <td width="100%" rowspan="4" valign="top">
+<?php if($browser->hasFeature('javascript')): ?>
+<?php
+$types = Fima::getAccountTypes();
+foreach($types as $typeId => $typeLabel):
+    if ($typeId == FIMA_ACCOUNTTYPE_ASSET) {
+        continue;
+    }
+?>
+   <input name="account_type_<?php echo $typeId ?>" id="account_type_<?php echo $typeId ?>" onclick="selectAccounts('posting_account[]', '<?php echo $typeId ?>', this.checked)" type="checkbox" value="1" /> <?php echo Horde::img($typeId . '.png', $typeLabel) ?> <?php echo $typeLabel ?><br />
+<?php endforeach; ?>
+<?php endif; ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+    <strong><?php echo Horde::label('period_start', _("P_eriod")) ?></strong>
+  </td>
+  <td class="nowrap">
+   <?php echo Fima::buildDateWidget('period_start', $params['period_start'], null, false, true) ?>
+   &nbsp; - &nbsp
+   <?php echo Fima::buildDateWidget('period_end', $params['period_end'], null, false, true) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+    <strong><?php echo Horde::label('reference_start', _("Re_ference")) ?></strong>
+  </td>
+  <td class="nowrap">
+   <?php echo Fima::buildDateWidget('reference_start', $params['reference_start'], null, false, true) ?>
+   &nbsp; - &nbsp
+   <?php echo Fima::buildDateWidget('reference_end', $params['reference_end'], null, false, true) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+    <strong><?php echo _("Other") ?></strong>
+  </td>
+  <td class="nowrap">
+   <input name="cumulate" type="hidden" value="0" />
+   <input name="cumulate" type="checkbox" value="1" id="cumulate" <?php if ($params['cumulate']) { echo 'checked="checked"'; } ?>/>
+   <?php echo Horde::label('cumulate', _("C_umulate")) ?>
+   &nbsp;
+   <input name="subaccounts" type="hidden" value="0" />
+   <input name="subaccounts" type="checkbox" value="1" id="subaccounts" <?php if ($params['subaccounts']) { echo 'checked="checked"'; } ?>/>
+   <?php echo Horde::label('subaccounts', _("_Subaccounts")) ?>
+   &nbsp;
+   <input name="nullrows" type="hidden" value="0" />
+   <input name="nullrows" type="checkbox" value="1" id="nullrows" <?php if ($params['nullrows']) { echo 'checked="checked"'; } ?>/>
+   <?php echo Horde::label('nullrows', _("_Null Rows")) ?>
+   &nbsp;
+   <input name="yearly" type="hidden" value="0" />
+   <input name="yearly" type="checkbox" value="1" id="yearly" <?php if ($params['yearly']) { echo 'checked="checked"'; } ?>/>
+   <?php echo Horde::label('yearly', _("_Yearly")) ?>
+<?php if ($graphs): ?>
+   &nbsp;
+   <input name="graph" type="hidden" value="0" />
+   <input name="graph" type="checkbox" value="1" id="graph" <?php if ($params['graph']) { echo 'checked="checked"'; } ?>/>
+   <?php echo Horde::label('graph', _("_Chart")) ?>
+<?php endif; ?>
+  </td>
+ </tr>
+</table>
+<br />
+
+<input type="submit" class="button" value="<?php echo _("Open Report") ?>" />
+<input type="submit" class="button" value="<?php echo _("Reset") ?>" onclick="document.report.actionID.value='clear_report';" />
+
+</form>
diff --git a/fima/templates/reports/table.inc b/fima/templates/reports/table.inc
new file mode 100644 (file)
index 0000000..fd43e2b
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+if (!isset($report)) {
+return;
+}
+
+$data = $report->getData();
+
+$reporturl = $report->getParam('url');
+$sortby = $report->getParam('sortby');
+$sortdir = $report->getParam('sortdir');
+$sorturl = Util::addParameter($reporturl, 'sortdir', ($sortdir) ? 0 : 1);
+$sortImg = ($sortdir) ? 'za.png' : 'az.png';
+$sortImgText = ($sortdir) ? '\/' : '/\\';
+
+?>
+<br />
+<table class="reportTable" cellspacing="0">
+<?php
+foreach ($data as $rowId => $row) {
+    echo '<tr class="' . (preg_match('/__result(.*)?__/', $rowId) ? 'result' : 'item') . '">';
+    foreach ($row as $colId => $value) {
+        if ($rowId === '__headersort__') {
+            $sortText = sprintf(_("Sort by %s"), $value);
+            echo '<th class="' . (($sortby == $colId) ? 'selected' : 'item') . ' sortable" onclick="document.location.href=' . addslashes(Util::addParameter(($sortby == $colId) ? $sorturl : $reporturl, 'sortby',  $colId)) . '">';
+            if ($sortby == $colId) {
+                echo Horde::link(Util::addParameter($sorturl, 'sortby', $colId), $sortText, null, null, null, $sortText) . Horde::img($sortImg, $sortImgText, '', $registry->getImageDir('horde')) . '</a>';
+            }
+            echo Horde::widget(Util::addParameter(($sortby == $colId) ? $sorturl : $reporturl, 'sortby', $colId), $sortText, 'widget', null, null, $value);
+            echo '</th>';
+        } elseif ($rowId === '__header__') {
+            echo '<th class="item">' . htmlspecialchars($value) . '</th>';
+        } elseif ($colId === '__header__') {
+            echo '<td>' . ($value{0} === ' ' ? '&nbsp; &nbsp; ' : '') . htmlspecialchars($value) . '</td>';
+        } elseif ($value === null) {
+            echo '<td></td>';
+        } else {
+            echo '<td class="' . (($value < 0) ? 'negative' : 'positive') . ' amount">' . Fima::convertValueToAmount($value) . '</td>';
+        }
+    }
+    echo "</tr>\n";
+}
+?>
+</table>
+
diff --git a/fima/templates/search/search.inc b/fima/templates/search/search.inc
new file mode 100644 (file)
index 0000000..a67e266
--- /dev/null
@@ -0,0 +1,109 @@
+<form method="post" name="search" action="postings.php">
+<?php Util::pformInput() ?>
+<input type="hidden" name="actionID" value="search_postings" />
+
+<div class="header">
+<?php echo htmlspecialchars($title) ?>
+<?php if (isset($_SESSION['fima_search'])): ?>
+<?php echo Horde::link(Util::addParameter(Horde::applicationUrl('search.php'), 'actionID', 'clear_search'), _("Clear Search Query")) . Horde::img('delete.png', _("Clear Search Query"), '', $registry->getImageDir('horde')) ?></a>
+<?php endif; ?>
+</div>
+
+<table cellspacing="0" width="100%" class="linedRow">
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('search_type', _("Search Posting T_ype")) ?></strong>&nbsp;
+  </td>
+  <td width="100%" colspan="2">
+   <select name="search_type" id="search_type">
+<?php foreach($types as $typeId => $typeLabel): ?>
+    <option value="<?php echo $typeId ?>" <?php if ($typeId == $search['type']) { echo 'selected="selected"'; } ?>><?php echo $typeLabel; ?></option>
+<?php endforeach; ?>
+   </select>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap" valign="top">
+    <strong><?php echo Horde::label('search_date_start', _("Search _Date Start")) ?></strong>&nbsp;
+  </td>
+  <td class="nowrap" width="20%">
+   <?php echo Fima::buildDateWidget('search_date_start', $search['date_start']) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap" valign="top">
+    <strong><?php echo Horde::label('search_date_end', _("Search Dat_e End")) ?></strong>&nbsp;
+  </td>
+  <td class="nowrap" width="20%">
+   <?php echo Fima::buildDateWidget('search_date_end', $search['date_end']) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap" valign="top">
+    <strong><?php echo Horde::label('search_asset[]', _("Search A_sset Accounts")) ?></strong>&nbsp;
+  </td>
+  <td width="100%" colspan="2" valign="top">
+   <?php echo Fima::buildAccountWidget('search_asset[]', $search['asset'], '', false, true, array(array('type', FIMA_ACCOUNTTYPE_ASSET))) ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap" valign="top">
+    <strong><?php echo Horde::label('search_account[]', _("Search Posting A_ccounts")) ?></strong>&nbsp;
+  </td>
+  <td width="20%" valign="top">
+   <?php echo Fima::buildAccountWidget('search_account[]', $search['account'], '', false, true) ?>
+  </td>
+  <td valign="top">
+<?php if($browser->hasFeature('javascript')): ?>
+<?php
+$types = Fima::getAccountTypes();
+foreach($types as $typeId => $typeLabel):
+?>
+   <input name="account_type_<?php echo $typeId ?>" id="account_type_<?php echo $typeId ?>" onclick="selectAccounts('search_account[]', '<?php echo $typeId ?>', this.checked)" type="checkbox" value="1" /> <?php echo Horde::img($typeId . '.png', $typeLabel) ?> <?php echo $typeLabel ?><br />
+<?php endforeach; ?>
+<?php endif; ?>
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('search_desc', _("Search Descriptio_n")) ?></strong>&nbsp;
+  </td>
+  <td width="100%" colspan="2">
+   <input name="search_desc" id="search_desc" type="text" value="<?php echo $search['desc'] ?>" size="50" maxlength="250" />
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('search_amount_start', _("Search Amo_unt Start")) ?></strong>&nbsp;
+  </td>
+  <td width="100%" colspan="2">
+   <input name="search_amount_start" id="search_amount_start" type="text" value="<?php echo $search['amount_start'] ?>" onblur="formatAmount(this);" size="15" maxlength="15" class="amount" />
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('search_amount_end', _("Search Amoun_t End")) ?></strong>&nbsp;
+  </td>
+  <td width="100%" colspan="2">
+   <input name="search_amount_end" id="search_amount_end" type="text" value="<?php echo $search['amount_end'] ?>" onblur="formatAmount(this);" size="15" maxlength="15" class="amount" />
+  </td>
+ </tr>
+ <tr>
+  <td class="rightAlign nowrap">
+   <strong><?php echo Horde::label('search_eo', _("Search e.o. Postin_gs")) ?></strong>&nbsp;
+  </td>
+  <td width="100%" colspan="2">
+   <select name="search_eo" id="search_eo">
+<?php foreach($eos as $eoId => $eoLabel): ?>
+    <option value="<?php echo $eoId ?>" <?php if ($eoId == $search['eo']) { echo 'selected="selected"'; } ?>><?php echo $eoLabel; ?></option>
+<?php endforeach; ?>
+   </select>
+  </td>
+ </tr>
+</table>
+<br />
+
+<input type="submit" class="button" value="<?php echo _("Search") ?>" />
+<input type="reset" class="button" value="<?php echo _("Clear Form") ?>" />
+
+</form>
diff --git a/fima/themes/bluewhite/screen.css b/fima/themes/bluewhite/screen.css
new file mode 100644 (file)
index 0000000..bb697ae
--- /dev/null
@@ -0,0 +1,4 @@
+/**
+ * $Horde: fima/themes/bluewhite/screen.css,v 1.0 2008/07/05 17:26:13 trt Exp $
+ */
+
diff --git a/fima/themes/graphics/accounts.png b/fima/themes/graphics/accounts.png
new file mode 100644 (file)
index 0000000..faabd81
Binary files /dev/null and b/fima/themes/graphics/accounts.png differ
diff --git a/fima/themes/graphics/accounts.psd b/fima/themes/graphics/accounts.psd
new file mode 100644 (file)
index 0000000..938438e
Binary files /dev/null and b/fima/themes/graphics/accounts.psd differ
diff --git a/fima/themes/graphics/add.png b/fima/themes/graphics/add.png
new file mode 100644 (file)
index 0000000..0c15a91
Binary files /dev/null and b/fima/themes/graphics/add.png differ
diff --git a/fima/themes/graphics/asset.png b/fima/themes/graphics/asset.png
new file mode 100644 (file)
index 0000000..6aae64d
Binary files /dev/null and b/fima/themes/graphics/asset.png differ
diff --git a/fima/themes/graphics/eoasset.png b/fima/themes/graphics/eoasset.png
new file mode 100644 (file)
index 0000000..6aae64d
Binary files /dev/null and b/fima/themes/graphics/eoasset.png differ
diff --git a/fima/themes/graphics/eoexpense.png b/fima/themes/graphics/eoexpense.png
new file mode 100644 (file)
index 0000000..768acdf
Binary files /dev/null and b/fima/themes/graphics/eoexpense.png differ
diff --git a/fima/themes/graphics/eoincome.png b/fima/themes/graphics/eoincome.png
new file mode 100644 (file)
index 0000000..e31032d
Binary files /dev/null and b/fima/themes/graphics/eoincome.png differ
diff --git a/fima/themes/graphics/expense.png b/fima/themes/graphics/expense.png
new file mode 100644 (file)
index 0000000..8173fbe
Binary files /dev/null and b/fima/themes/graphics/expense.png differ
diff --git a/fima/themes/graphics/fima.png b/fima/themes/graphics/fima.png
new file mode 100644 (file)
index 0000000..0b6551e
Binary files /dev/null and b/fima/themes/graphics/fima.png differ
diff --git a/fima/themes/graphics/income.png b/fima/themes/graphics/income.png
new file mode 100644 (file)
index 0000000..9a04797
Binary files /dev/null and b/fima/themes/graphics/income.png differ
diff --git a/fima/themes/graphics/list.png b/fima/themes/graphics/list.png
new file mode 100644 (file)
index 0000000..0b6551e
Binary files /dev/null and b/fima/themes/graphics/list.png differ
diff --git a/fima/themes/graphics/new-small.png b/fima/themes/graphics/new-small.png
new file mode 100644 (file)
index 0000000..3ee8e4c
Binary files /dev/null and b/fima/themes/graphics/new-small.png differ
diff --git a/fima/themes/graphics/posting.psd b/fima/themes/graphics/posting.psd
new file mode 100644 (file)
index 0000000..7aba1fd
Binary files /dev/null and b/fima/themes/graphics/posting.psd differ
diff --git a/fima/themes/graphics/report.png b/fima/themes/graphics/report.png
new file mode 100644 (file)
index 0000000..7a4adb7
Binary files /dev/null and b/fima/themes/graphics/report.png differ
diff --git a/fima/themes/graphics/reports.psd b/fima/themes/graphics/reports.psd
new file mode 100644 (file)
index 0000000..98cb1e1
Binary files /dev/null and b/fima/themes/graphics/reports.psd differ
diff --git a/fima/themes/graphics/search-small.png b/fima/themes/graphics/search-small.png
new file mode 100644 (file)
index 0000000..5bc6f0c
Binary files /dev/null and b/fima/themes/graphics/search-small.png differ
diff --git a/fima/themes/report.inc b/fima/themes/report.inc
new file mode 100644 (file)
index 0000000..6331316
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+$graphsize = explode('x', $GLOBALS['prefs']->getValue('report_graphsize'));
+
+$style = array('width' => $graphsize[0],
+               'height' => $graphsize[1],
+               
+               'font-family' => 'Verdana',
+               'font-size' => '8',
+               'font-color' => '#000000',
+               'header-size' => '14',
+               'subheader-size' => '11',
+               
+               'line' => '#888888',
+               'grid' => '#eeeeee',
+               
+               'color0' => '#ffff99',
+               'color1' => '#ccffcc',
+               'color2' => '#99ccff',
+               'color3' => '#ffcc99',
+               'color4' => '#ff99cc',
+               'color5' => '#ccffff',
+               'color6' => '#cc99ff',
+               'color7' => '#ffffcc',
+               'color8' => '#00ff00',
+               'color9' => '#3366ff',
+               'color10' => '#ffcc00',
+               'color11' => '#ff00ff',
+               'color12' => '#00ffff',
+               'color13' => '#993366',
+               'color14' => '#99cc00',
+               'color15' => '#339966',
+               'color16' => '#333399',
+               'color17' => '#ff6600',
+               'color18' => '#ff0000',
+               'color19' => '#33cccc',
+               'color20' => '#800080',
+               'color21' => '#808000',
+               'color22' => '#008000',
+               'color23' => '#000080',
+               'color24' => '#993300',
+               'color25' => '#800000',
+               'color26' => '#008080',
+               'color27' => '#666699',
+               'color28' => '#c0c0c0',
+               'color29' => '#808080',
+               'color30' => '#333333',
+               
+               'asset' => '#f3f3f3',
+               'income0' => '#ccffcc',
+               'income1' => '#99ee99',
+               'incometotal' => '#33cc33',
+               'expense0' => '#ffcccc',
+               'expense1' => '#ffaaaa',
+               'expensetotal' => '#ff6666',
+               
+               'actual' => '#ffe448', // yellow
+               'forecast' => '#4f8bc6', // blue
+               'budget' => '#ee8800', // orange
+               'reference' => '#db5ffd', // purple
+               
+               'difference' => '#cccccc', // grey
+               '%' => '#999999', // light grey
+               
+               '__resulttotal__' => '#888888',
+               '__rest__' => '#888888',
+               '__rest1__' => '#888888',
+               '__blank__' => '#ffffff',
+               );
diff --git a/fima/themes/screen.css b/fima/themes/screen.css
new file mode 100644 (file)
index 0000000..48cdb85
--- /dev/null
@@ -0,0 +1,295 @@
+/**
+ * $Horde: fima/themes/screen.css,v 1.0 2008/05/06 19:54:49 trt Exp $
+ */
+
+/* Align styles, added for BC. */
+.leftAlign {
+    text-align: left;
+}
+.rightAlign {
+    text-align: right;
+}
+.leftFloat {
+    float: left;
+}
+.rightFloat {
+    float: right;
+}
+
+/* Added for BC. */
+.hidden {
+    display: none;
+}
+
+.radio {
+    border: 0;
+    height: 14px;
+    width: 14px;
+    background: transparent;
+}
+
+/* Menu bottom margin, added for BC. */
+#menu {
+    margin-bottom: 8px;
+}
+
+/* Image alignment, added for BC. */
+img {
+    vertical-align: middle;
+}
+
+/* Table CSS, added for BC. */
+th {
+    color: #333;
+    font-size: 90%;
+    border-bottom: 1px solid #999;
+}
+
+/* Sort arrow styles, added for BC. */
+.sortup {
+    background: #bbcbff url("graphics/za.png") center left no-repeat;
+    padding-left: 10px;
+}
+.sortdown {
+    background: #bbcbff url("graphics/az.png") center left no-repeat;
+    padding-left: 10px;
+}
+
+/* Account list styles. */
+#t_account_tree {
+       font-color: inherit;
+       background-color: #fff;
+}
+
+/* Message list table styles. */
+.postingList {
+    table-layout: fixed;
+    width: 100%;
+       border-collapse: collapse;
+}
+.postingList tr {
+    height: 20px;
+}
+.postingList th {
+    text-align: left;
+    cursor: pointer;
+    white-space: nowrap;
+       border: 1px solid #eee;
+}
+.postingList td {
+       padding-top: 0;
+       padding-bottom: 0;
+       text-indent: 1px;
+    white-space: nowrap;
+       border: 1px solid #ddd;
+       vertical-align: middle;
+}
+.postingList td.ohide {
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+.postingList img {
+    vertical-align: middle;
+}
+
+/* Report table styles. */
+.reportTable {
+}
+
+.reportTable tr {
+    height: 20px;
+       color: inherit;
+    background-color: #fff;
+}
+.reportTable th {
+       padding: 0 5px 0 5px;
+    text-align: left;
+    white-space: nowrap;
+       border: 1px solid #eee;
+}
+.reportTable th.sortable {
+    cursor: pointer;
+}
+.reportTable td {
+       padding: 0 5px 0 5px;
+       text-indent: 1px;
+    white-space: nowrap;
+       border: 1px solid #ddd;
+       border-top: 0px none #000;
+       border-right: 0px none #000;
+       vertical-align: middle;
+       border-collapse: collapse;
+}
+.reportTable tr.result {
+       color: inherit;
+       background-color: #eee;
+    /*font-weight: bold;*/
+}
+
+/* Style for div that fixes KHTML browsers. */
+.ohide {
+    overflow: hidden;
+}
+
+/* Navbar action styling. */
+ul.msgactions, ul.msgactions ul {
+    list-style: none;
+}
+.msgactions li {
+    float: left;
+    padding: 1px 3px;
+    border-right: 1px solid #888;
+    position: relative;
+}
+.msgactions li.lastnavbar {
+    border-right: 0;
+}
+.msgactions li a {
+    font-size: 90%;
+    white-space: nowrap;
+}
+.msgactions li ul {
+    display: none;
+    position: absolute;
+    left: -1px;
+    margin-top: -2px;
+    padding: 2px;
+    background: #f4f4f4;
+    border: 1px solid #d4d4d4;
+    z-index: 99;
+}
+.bottomborder .msgactions li ul {
+    top: 1.25em;
+}
+.topborder .msgactions li ul {
+    bottom: 1.25em;
+}
+.msgactions li ul a:hover, .msgactions li ul a:active {
+    background: gray;
+    color: #fff;
+}
+.msgactions li:hover ul, .msgactions li.hover ul {
+    display: block;
+}
+.mboxcontrol {
+    color: #000;
+    background: #ccc;
+}
+.bottomborder {
+    border-bottom: 1px solid #999;
+}
+.bottombordercollapse {
+    border-bottom: 2px solid #999;
+}
+.topborder {
+    border-top: 1px solid #999;
+}
+#msgheaders {
+    clear: left;
+}
+
+/* Posting styles. */
+.postingInfo {
+       color: inherit;
+       background-color: #eee;
+    /*font-weight: bold;*/
+}
+span.postingResult {
+    display: block;
+    width: 90px;
+    text-align: right;
+    white-space: nowrap;
+}
+.posting {
+    background: #fff;
+}
+.asset {
+    background: #fff;
+}
+tr.asset:hover, .asset-over {
+    background: #f3f3f3;
+}
+.eoasset {
+    background: #cfcfcf;
+}
+tr.eoasset:hover, .eoasset-over {
+    background: #afafaf;
+}
+.income {
+    background: #fff;
+}
+tr.income:hover, .income-over {
+    background: #f3f3f3;
+}
+.expense {
+    background: #fff;
+}
+tr.expense:hover, .expense-over {
+    background: #f3f3f3;
+}
+.eoincome {
+    background: #cfc;
+}
+tr.eoincome:hover, .eoincome-over {
+    background: #9e9;
+}
+.eoexpense {
+    background: #fcc;
+}
+tr.eoexpense:hover, .eoexpense-over {
+    background: #faa;
+}
+.closed {
+    color: #8f8f8f; background-color: inherit;
+}
+.amount {
+    text-align: right;
+}
+.positive {
+    color: #000; background-color: inherit;
+}
+.negative {
+    color: #e00; background-color: inherit;
+}
+
+/* Tables. */
+table#ledger-list {
+    width: 99%;
+    margin: 0 0 8px 5px;
+    border-top: 1px solid #ddd;
+    border-left: 1px solid #ddd;
+}
+table#ledger-list th {
+    padding: 3px;
+    background: #e9e9e9;
+    border-right: 1px solid #ccc;
+    text-align: left;
+}
+table#ledger-list td {
+    padding: 3px;
+    border-right: 1px solid #ddd;
+    border-bottom: 1px solid #ddd;
+}
+table#ledger-list th.sortup {
+    background: #bbcbff url("graphics/za.png") center left no-repeat;
+    padding-left: 10px;
+}
+table#ledger-list th.sortdown {
+    background: #bbcbff url("graphics/az.png") center left no-repeat;
+    padding-left: 10px;
+}
+
+#ledger-list-buttons {
+    padding: 1em;
+}
+#ledger-list-buttons form {
+    display: inline;
+}
+.ledger-list-icon {
+    width: 1%;
+}
+
+.ledger-info {
+    cursor: pointer;
+}
+
diff --git a/fima/themes/silver/graphics/accounts.png b/fima/themes/silver/graphics/accounts.png
new file mode 100644 (file)
index 0000000..2cc055d
Binary files /dev/null and b/fima/themes/silver/graphics/accounts.png differ
diff --git a/fima/themes/silver/graphics/accounts.psd b/fima/themes/silver/graphics/accounts.psd
new file mode 100644 (file)
index 0000000..cd004eb
Binary files /dev/null and b/fima/themes/silver/graphics/accounts.psd differ
diff --git a/fima/themes/silver/graphics/add.png b/fima/themes/silver/graphics/add.png
new file mode 100644 (file)
index 0000000..eb28547
Binary files /dev/null and b/fima/themes/silver/graphics/add.png differ
diff --git a/fima/themes/silver/graphics/asset.png b/fima/themes/silver/graphics/asset.png
new file mode 100644 (file)
index 0000000..0e225b9
Binary files /dev/null and b/fima/themes/silver/graphics/asset.png differ
diff --git a/fima/themes/silver/graphics/eoasset.png b/fima/themes/silver/graphics/eoasset.png
new file mode 100644 (file)
index 0000000..0e225b9
Binary files /dev/null and b/fima/themes/silver/graphics/eoasset.png differ
diff --git a/fima/themes/silver/graphics/eoexpense.png b/fima/themes/silver/graphics/eoexpense.png
new file mode 100644 (file)
index 0000000..adb1767
Binary files /dev/null and b/fima/themes/silver/graphics/eoexpense.png differ
diff --git a/fima/themes/silver/graphics/eoincome.png b/fima/themes/silver/graphics/eoincome.png
new file mode 100644 (file)
index 0000000..24b64cf
Binary files /dev/null and b/fima/themes/silver/graphics/eoincome.png differ
diff --git a/fima/themes/silver/graphics/expense.png b/fima/themes/silver/graphics/expense.png
new file mode 100644 (file)
index 0000000..59cd853
Binary files /dev/null and b/fima/themes/silver/graphics/expense.png differ
diff --git a/fima/themes/silver/graphics/fima.png b/fima/themes/silver/graphics/fima.png
new file mode 100644 (file)
index 0000000..59452c6
Binary files /dev/null and b/fima/themes/silver/graphics/fima.png differ
diff --git a/fima/themes/silver/graphics/income.png b/fima/themes/silver/graphics/income.png
new file mode 100644 (file)
index 0000000..9a4f44a
Binary files /dev/null and b/fima/themes/silver/graphics/income.png differ
diff --git a/fima/themes/silver/graphics/list.png b/fima/themes/silver/graphics/list.png
new file mode 100644 (file)
index 0000000..59452c6
Binary files /dev/null and b/fima/themes/silver/graphics/list.png differ
diff --git a/fima/themes/silver/graphics/new-small.png b/fima/themes/silver/graphics/new-small.png
new file mode 100644 (file)
index 0000000..e1557f5
Binary files /dev/null and b/fima/themes/silver/graphics/new-small.png differ
diff --git a/fima/themes/silver/graphics/posting.psd b/fima/themes/silver/graphics/posting.psd
new file mode 100644 (file)
index 0000000..aa89b61
Binary files /dev/null and b/fima/themes/silver/graphics/posting.psd differ
diff --git a/fima/themes/silver/graphics/report.png b/fima/themes/silver/graphics/report.png
new file mode 100644 (file)
index 0000000..c7749a8
Binary files /dev/null and b/fima/themes/silver/graphics/report.png differ
diff --git a/fima/themes/silver/graphics/report.psd b/fima/themes/silver/graphics/report.psd
new file mode 100644 (file)
index 0000000..0a79569
Binary files /dev/null and b/fima/themes/silver/graphics/report.psd differ
diff --git a/fima/themes/silver/graphics/search-small.png b/fima/themes/silver/graphics/search-small.png
new file mode 100644 (file)
index 0000000..a586615
Binary files /dev/null and b/fima/themes/silver/graphics/search-small.png differ
diff --git a/fima/themes/silver/screen.css b/fima/themes/silver/screen.css
new file mode 100644 (file)
index 0000000..d4848a8
--- /dev/null
@@ -0,0 +1,4 @@
+/**
+ * $Horde: fima/themes/silver/screen.css,v 1.0 2008/06/03 18:36:13 trt Exp $
+ */
+
diff --git a/fima/themes/silver/themed_graphics b/fima/themes/silver/themed_graphics
new file mode 100644 (file)
index 0000000..e69de29