]> lifelog.hopto.org Git - LifeLog.git/commitdiff
Merge 1.8.dev
authorWill Budic <redacted>
Tue, 3 Mar 2020 17:52:39 +0000 (04:52 +1100)
committerWill Budic <redacted>
Tue, 3 Mar 2020 17:52:39 +0000 (04:52 +1100)
23 files changed:
.gitignore
CNF_Specs.md
Current Development Check List.md
Installation.txt
README.md
dbLifeLog/main.cnf
htdocs/cgi-bin/config.cgi
htdocs/cgi-bin/configFileTester.pl [deleted file]
htdocs/cgi-bin/data.cgi [moved from htdocs/cgi-bin/remove.cgi with 62% similarity]
htdocs/cgi-bin/json.cgi
htdocs/cgi-bin/login_ctr.cgi
htdocs/cgi-bin/main.cgi
htdocs/cgi-bin/stats.cgi
htdocs/cgi-bin/system/modules/Settings.pm
htdocs/cgi-bin/wsrc/jquery.sweet-dropdown.css [new file with mode: 0644]
htdocs/cgi-bin/wsrc/jquery.sweet-dropdown.js [new file with mode: 0644]
htdocs/cgi-bin/wsrc/main.css
htdocs/cgi-bin/wsrc/main.js
htdocs/cgi-bin/wsrc/main_earth.css
htdocs/cgi-bin/wsrc/main_moon.css
htdocs/cgi-bin/wsrc/main_sun.css
stopDevWebServer.sh
thttpd.conf

index 440d59995272814ea11e37ccb08f49c43242cdb0..8178fe367f00bc86bed9735b02369001680c3420 100644 (file)
@@ -1,11 +1,14 @@
 .vscode
 .vstags
 log/thttpd.log
+*.log
+*.pid
 thttpd-2.29/
 thttpd-2.29/*
 startDevWebServer.sh
 thttpd.conf
 cgisess_*
+*.pid
 # Compiled source #
 ###################
 *.com
@@ -44,4 +47,17 @@ cgisess_*
 .Trashes
 *.db
 *.swp
+*.log
+
+log/thttpd.log
+.gitignore
+htdocs/thttpd.pid
+htdocs/cgi-bin/wsrc/examples.css
+dbLifeLog/*.db-journal
+log/*.log
+/home/will/dev/LifeLog/log/thttpd.log
+dbLifeLog/*.db.tgz
+dbLifeLog/*.db.toz
 
+*.db.osz
+dbLifeLog/bck/*
\ No newline at end of file
index 1df1d77fc8af88e1dbd88a0272746e760773c09a..d86aa10ddc589e6cda3085fd9ea9503f7b42575c 100644 (file)
 # Configuration Network File Format Specifications
 
- Moon Stage v.1.0
 
 ## Introduction
 
-This is a simple and fast file format. That allowes setting an network application with constant values.
-SQL database structures and data. It is designed to accomodate an parser to read and parse CNF tags.
-These can be of three types, using an textual similar presentation.
-And are recognised as constants, anons and sqlites.
-## CNF Formatting Rules
-
-* Text that isn't CNF tagged is ignored in the file and can be used as comments.
-* CNF tag begins with an **<<** and ends with an **>>**
-* CNF instructions and constants are uppercase.
-* CNF instructions are all uppercase and unique, to its processor.
-* A CNF constant in its propety name is prefixed with an '**$**' signifier.
-  * Constants are ususally scripted at the begining of the file, or parsed first in a separate file.
-  * The instruction processor can use them if signifier $ surounds the constant name. Therefore, replacing it with the contants value if further found in the file.
-
-        <<<CONST>$APP_PATH=~/MyApplication>>
-        <<app_path>$APP_PATH$/module/main>>
-  * CNF Constant values can't be changed for th life of the application.
-  * CNF Constant values can be changed in the file itself only.
-* A CNF Anon is to similar to constants but a more simpler property and value only pair.
-  * Anon is not instruction processed. Hence anonymouse in nature for its value.
-  * Anon has no signifier.
-  * Anon value is global to the application and its value can be modified.
-
-        <<USE_SWITCH>true>>
-        <<DIALOG_TITLE_EN>MyApplication Title>>
-
-* CNF supports basic SQL Database structure statment generation. This is done via instruction based CNF tags. Named sqlites.
-  * Supported is table, view, index and data creation statments.
-  * Database statments are generic text, that is not further processed.
-  * There is no database interaction, handling or processing as part of this processing.
+This is a simple and fast file format. That allows setting up of network applications with initial configuration values.
+These are usually standard, property and value pairs. Containing possible also SQL database structures statements with basic data.
+It is designed to accommodate a parser to read and provide for CNF property tags. These can be of three types, using all a textual similar presentation.
+In general are recognised as constants, anons or sqlites.
+
+Operating system environmental settings or variables are considered only as the last resort to provide for a value.
+This is however to be avoided as it hides the attention and expectation for a setting.
+
+With this type of an application configuration system. Global settings can also be individual scripted with an meaningful description.
+Which is pretty much welcomed and encouraged.
+
+## General CNF Formatting Rules
+
+1. Text that isn't CNF tagged is ignored in the file and can be used as comments.
+2. CNF tag begins with an **<<** or **<<<** and ends with an **>>>** or **>>**.
+3. If instruction is contained the tag begins with **<<** and ends with a **>>**.
+4. Multi line values are tag ended on a separate line with an **>>>**.
+5. CNF tag value can post processed by placing macros making it a template.
+6. Standard markup of a macro is to enclose the property name or number with a triple dollar signifier **\$\$\$**{macro}**\$\$\$**.
+    1. Presedence of resloving the property name/value is by first passed macros, then config anons and finally the looking up constances.
+    2. Nested macros resolving from linked in other properties is currently not supported.
+7. CNF instructions and constants are uppercase.
+    1. Example 1 format with instruction: ```<<<CONST\n{name=value\n..}\n>>>``` autonoumus const, with inner properties.
+    2. Example 2 format with instruction: ```<<{$sig}{NAME}<CONST {multi line value}>>>``` A single const property with a multi line value.
+    3. Example 3 format with instruction: ```<<CONST<{$sig}{NAME}\n {multi line value}>>>``` A single const property with a multi line value.
+    4. Example 4 format with instruction: ```<<{NAME}<{INSTRUCTION}<{value}>>>``` A anon.
+    5. Example 5 format with instruction: ```<<{$sig}{NAME}<{INSTRUCTION}\n{value}\n>>>```.
+
+8. CNF instructions are all uppercase and unique, to the processor.
+9. A CNF constant in its property name is prefixed with an '**$**' signifier.
+10. Constants are usually scripted at the beginning of the file, or parsed first in a separate file.
+11. The instruction processor can use them if signifier $ surrounds the constant name. Therefore, replacing it with the constants value if further found in the file.
+
+```HTML
+     <<<CONST $APP_PATH=~/MyApplication>>>
+     <<app_path<$APP_PATH$/module/main>>>
+ ```
+
+12. Property names, Constant, Anon refer to the programmatically assinged variable name.
+13. CNF Constant values are store specific.
+14. Constants can't be changed for the life of the application or service issued.
+15. Storage of CNF constants declared can be preceded to the file based one.
+16. i.e. If stored in a database or on a network node. After the file declaration fact.
+17. Missing file based storage settings can be next queried from the environmental one.
+    1. This is to be avoided if possible.
+18. File storage encountered constants override system environmental ones.
+    1. i.e. System administrator has set them.
+19. Database storage encountered constants override file set ones.
+    1. i.e. User of application has set them.
+20. CNF Constant values can be changed in the script file.
+    1. If not present in script file, then an application setting must procede with its default.
+    2. CNF Constants can be declared only once during initial parsing of script files.
+    3. Rule of thumb is that Constants are synchonized with an applications release version.
+    4. Static constants, are script or code only assigned values.
+    5. CNF Anons can overide in contrast previously assigned value.
+21. A CNF Anon is similar to constants but a more simpler property and value only pair.
+    1. Anons are so called because they are unknown or unexpected by the configuration framework, store to object intermidiate.
+    2. Constants that turn up in the anon list, are a good indicator that they are not handled from script. Forgotten become anons.
+    3. Anons similar to constants, once in the database, overtake the scripted or application default settings value.
+    4. Static anons, are those that are set in the database, and/or are not merged with application defaults.
+    5. Anons hashed are programatically accessed separately to constants.
+       1. It is fine to have several different applications, to share same storage, even if they have different implementation.
+       2. Contants will be specific to application, while anons can change in different purpose script files.
+22. Anon is not instruction processed. Hence anonymous in nature for its value.
+23. Anon has no signifier, and doesn't need to have an application default.
+24. Anon value is global to the application and its value can be modified.
+
+    ```HTML
+            <<USE_SWITCH<true>>>
+            <<DIALOG_TITLE_EN<MyApplication Title>>>
+    ```
+    1.  Anon value can be scripted to contain template like but numbered parameters.
+    2.  When querying for an anon value, replacement parameter array can be passed.
+    3.  Numbering is from **\$\$\$1\$\$\$**..**$$$(n...)\$\$\$** to be part of its value. Strategically placed.
+
+    ```HTML
+        <<GET_SUB_URL<https://www.$$$1$$$.acme.com/$$$2$$$>>>
+    ```
+    ```PERL
+       # Perl language
+       my $url = $cnf->anon('GET_SUB_URL',('tech','main.cgi'));
+       # $url now should be: https://www.tech.acme.com/main.cgi
+       eval ($url =~ m/https:\.*/)
+             or warn "Failed to obtain expected URL when querying anon -> GET_SUB_URL"
+    ```
 
 ## CNF Tag Formats
 
 ### Property Value Tag
 
-    <<{name}<{value}>>
+    ```HTML
+        <<{name}<{value}>>>
+    ```
 
 ### Instruction Value Tag
 
-    <<<{instruction}
-    {value\n...valuen\n}>>
+    ```HTML
+        <<<{INSTRUCTION}
+        {value\n...valuen\n}>>>
+    ```
 
 ### Full Tag
 
-    <<{name}>{instruction}
-        {value\n...value\n}
-    >>
+    ```javascript
+        <<{$sig}{name}<{INSTRUCTION}
+            {value\n...value\n}
+        >>>
+    ```
 
 **Examples:**
 
-    <<CONST>$HELP
-        Sorry help is currently.
-        Not available.
-    >>
-    <<<CONST
-        $RELEASE_VER = 1.8
-        $SYS_1 =    10
-        $SYS_2 = 20
-        $SYS_3 =      "   Some Nice Text!   "
-    >>
-    <<PRINT_TO_HELP<true>>
+    ```HTML
+        <<$HELP<CONST
+            Sorry help is currently.
+            Not available.
+        >>
+        <<<CONST
+            $RELEASE_VER = 1.8
+            $SYS_1 =    10
+            $SYS_2 = 20
+            $SYS_3 =      "   Some Nice Text!   "
+        >>
+        <<PRINT_TO_HELP<true>>
+    ```
+
+## Database and SQL Instruction Formatting
+
+(Note - this documentation section not complete, as of 2020-02-14)
+
+### About
+
+CNF supports basic SQL Database structure statement generation. This is done via instruction based CNF tags. Named **sqlites**.
 
-## SQL Instruction Formatting
+1. Supported is table, view, index and data creation statements.
+2. Database statements are generic text, that is not further processed.
+3. There is limited database interaction, handling or processing to be provided.
+   1. Mainly for storage transfer of CNF constants, from file to database.
+   2. File changes precede database storage only in newly assigned constants.
+4. Database generated is expected to have a system  SYS_CNF_CONFIG table, containing the constants unique name value pairs, with optional description for each.
+   1. This is a reserved table and name.
+   2. This table must contain a **$RELEASE_VER** constants record at least.
 
-(section not complete, as of 2020-02-04)
+### SQLite Formatting
 
-* SQLites have the following reserved instructions.
-  * TABLE
+* SQLites have the following reserved instructions:
+1. TABLE
 
-        <<MyAliasTable>TABLE
+    ```HTML
+        <<MyAliasTable<TABLE
                     ID INT PRIMARY KEY NOT NULL,
                     ALIAS VCHAR(16) UNIQUE CONSTRAINT,
                     EMAIL VCHAR(28),
                     FULL_NAME VCHAR(128)
-        >>
-
-  * INDEX
+        >>>
+    ```
+2. INDEX
 
+    ```HTML
         <<MyAliasTable<INDEX
             idx_alias on MyAliasTable (ALIAS);
+        >>>
+    ```
+3. SQL
+     1. SQL statements are actual full SQL placed in the tag body value.
+
+    ```HTML
+        <<VW_ALIASES>VIEW
+            CREATE VIEW VW_ALIASES AS SELECT ID,ALIAS ORDER BY ALIAS;
+        >>>
+    ```
+
+4. DATA
+    1. Data rows are ended with the **~** delimiter. In the tag body.
+    2. Data columns are delimited with the invert quote **`** (back tick) within a row.
+    3. These should appear as last in the config file as they are translated into insert statements.
+    4. First column is taken as the unique and record identity column (UID).
+    5. Data is to be updated in storage if any column other than the UID, has its contents changed in the file.
+      1. This behaviour can be controlled by disabling something like  an auto file storage update. i.e. during application upgrades. To prevent user set settings to reset to factory defaults.
+      2. The result would then be that database already stored data remains, and only new ones are added. This exercise is out of scope of this specification.
+
+    ```HTML
+        <<MyAliasTable<DATA
+        01`admin`admin@inc.com`Super User
+        02`chef`chef@inc.com`Bruno Allinoise
+        03`juicy`sfox@inc.com`Samantha Fox
         >>
+    ```
+
+1. FILE
+    1.  Expects a file name assigned value, file containing actual further CNF DATA rows instructions, separately.
+    2.  The file is expected to be located next to the main config file.
+    3.  File is to be sequentially buffer read and processed instead as a whole in one go.
+    4.  The same principles apply in the file as to the DATA instruction CNF tag format, expected to be contained in it.
 
-  * SQL
-    * SQL statments are actual full SQL statments placed in the tag body.
+    ```HTML
+        <<MyItemsTbl<FILE data_my_app.cnf>
+    ```
 
-            <<VW_ALIASES>VIEW
-                CREATE VIEW VW_ALIASES AS SELECT ID,ALIAS ORDER BY ALIAS;
-            >>
+2. MIGRATE
+   1. Migration are brute sql statements to be run based on currently installed previous version of the SQL database.
+   2. Migration is to be run from version upwards as stated and in the previous database encountered.
+      1. i.e. If encountered old v.1.5, it will be upgraded to v.1.6 first, then v.1.7...
+   3.  Migration is not run on newly created databases. These create the expected latest data structure.
+   4.  SQL Statements a separated by ';' terminator. To be executed one by one.
 
-  * DATA
-    * Data columns are delimited with the **`** delimiter. In the tag body.
-    * These should apear as last in the config file as they are translated into insert statements.
+    ```HTML
+        <<1.6<MIGRATE
+                ALTER TABLE LOG ADD STICKY BOOL DEFAULT 0;
+        >>
+        <<1.8<MIGRATE
+            CREATE TABLE notes_temp_table (LID INTEGER PRIMARY KEY NOT NULL, DOC TEXT);
+            INSERT INTO notes_temp_table SELECT `LID`,`DOC` FROM `NOTES`;
+            DROP TABLE `NOTES`;
+            ALTER TABLE `notes_temp_table` RENAME TO `NOTES`;
+        >>
+    ```
 
-            <<MyAliasTable<DATA
-                01`admin`admin@inc.com`Super User
-                02`chef`chef@inc.com`Bruno Allinoise
-                03`juicy`sfox@inc.com`Samantha Fox
-            >>
+## Sample Perl Language Usage
 
+**~/my_application.pl** file contents:
+```PERL
+
+use lib "system/modules";
+use lib $ENV{'PWD'}.'/perl_dev/WB_CNF/system/modules';
+require CNFParser;
+require Settings;
+
+my @expected = ("$MY_APP_LIB_RELATIVE", "$MY_APP_DB_RELATIVE");
+my $path = $ENV{'PWD'}."/perl_dev/WB_CNF/db/configuration.cnf";
+# Loading twice config here with object constructor with and without path.
+# To show dual purpose use.
+my $cnf1  = CNFParser->new($path);
+# Nothing parsed yet construct.
+my $cnf2  = CNFParser->new();
+   # We relay that the OS environment has been set for CNF constant settings if missing
+   # in the configuration file. Adding after parse has no effect if found in file.
+   $cnf2 -> addENVList(@expected);
+   # Parse finally now. Parse can be called on multiple different files, if desired.
+   $cnf2 -> parse($path);
+my $LIB_PATH;
+
+print "List of constants in file: $path\n";
+foreach my $prp ($cnf->constants()){
+    print "$prp=", $cnf->constant($prp),"\n";
+}
+if(!$cnf->constant('$MY_APP_LIB_RELATIVE')){
+    warn 'Missing $MY_APP_LIB_RELATIVE setting.';
+    $LIB_PATH = $cnf2->constant('$MY_APP_LIB_RELATIVE');
+    die  'Unable to get required $MY_APP_LIB_RELATIVE setting!' if(not $LIB_PATH)
+}
+
+print "Welcome to ", $cnf->constant('$APP_NAME'), " version ", $cnf->constant('$RELEASE_VER'), ".\n";
+
+```
+**~//perl_dev/WB_CNF/db/configuration.cnf** file contents:
+```HTML
+# List command anon with the name of 'list'.
+<<list>ls -lh dev|sort>
+<<<CONST
+$RELEASE_VER = 1.0
+$APP_NAME="My Application Sample"
+>>>
+
+```
 
 ***
 
+   Document is from project ->  <https://github.com/wbudic/LifeLog/>
+
+   An open source application.
 
+<center>Moon Stage - v.1.1 2020</center>
 
-     Project ->  <https://github.com/wbudic/LifeLog/>
index e0b66405aa7ce17d9fbade5201a6b80fbfc12186..d7071dad7a80d1dc80422020bca72f522d633ee2 100644 (file)
@@ -1,4 +1,4 @@
-# Branch Development LifeLog in Perl - Sun Stage v. 1.7
+# Branch Development LifeLog in Perl
 
 *This page lists current development and issues being worked on in the LifeLog app. Being in the **Sun** stage, means there is a production environment. And usable, used. When, the project reaches **Earth** stage. It will be at its final specification. No data structures or major new features can be added or requested anymore. Only bug fixes, enhancements and efficiency fixes, if any at the **Earth** stage.*
 
@@ -9,7 +9,29 @@ This version is not compatible in data structure to prior versions. Data migrati
 
 ### v.1.7 Encountered
 
+* &#10004; Database backup tgz ball, upload and download button on config page.
+  * You must have the password you logged in to unscramble the backup.
+    * Alias -> pass -> backup password. Information required.
+* Application log needed in the background for System based logs.
 * New CNF Development.
+  * &#10004; Migration is currently hard to maintain and data export and import is wrongly reliant to CVS.
+  * &#10004; Anons to be enabled.
+  * CVS imports/exports are to be made obsolete in the future. It is not safe.
+    * This will be still available via command line.
+* RTF Documents header lister page, to provide for, new log entry assignment, deletion, edits.
+  * There isnt and shouldn't be a full relationship to docs. Hence new log entries can link to existing, docs.
+* &#10004;Use the pages cat_list meta data elements for dealing with categories in java scripts.
+* &#10004; New Categories dropdown, grouping in ascending order and presenting in columns of five at a time.
+* &#10004; Sticky rows bg colour, to be a shade different to other normal rows.
+* &#10004; Login system log and out to be implemented. With system variable $TRACK_LOGINS to disable/enable.
+* &#10004;Change all code to use Exceptions as project is becoming hard to manage.
+  * The harder it is to foresee possible problems, the less likely you will add unnecessary complexity. -- bud@
+* &#10004; Notes to Log table should be other way in relationship direction.
+  * LOG.ID_RTF -> NOTES.rowid
+  * This is currently causing problems when the log renumerates, or entries are imported.
+* &#10004; In config page Categories section to appear after system settings. As less likelly to be changed.
+  * System Configuration section is to be sorted. As in future it is more likelly to grow.
+* &#10004; New system setting, $VIEW_ALL_LMT=1000. To limit view all records displayed on huge databases.
 * &#10004; Provide system logs on stats page runs.
 * &#10004; Menus updated in other pages to have button look.
 * &#10004; main.cnf newer versions should have precedence to id and entry name to previously set or stored in db.
@@ -20,8 +42,6 @@ This version is not compatible in data structure to prior versions. Data migrati
 * &#10004; Mutli new alias access flood attack security trigger implementation.
 * &#10004; Debug system settings implementaiton.
 * &#10004; Delete page updated to show better display of entries.
-* Provide sub alias login that sets data visible to only a set of categories.
-  * View specific based login on a different password.
 * &#10004; Login page to indentify host.
 * &#10004; Session cleanup on autologin not clearing properly.
   * A dbfix, should clear older entries as well.
@@ -29,18 +49,31 @@ This version is not compatible in data structure to prior versions. Data migrati
 ### v.1.6 and less
 
 * $CUR_MTH_SVIEW - Start view page is for current month, and the sticky set.
-* Some System settings to be stored in session. As these are  known even before logon.
+* &#10004; Some System settings to be stored in session. As these are  known even before logon.
   * i.e. $SESSN_EXPR, $RELEASE_VER, $TIME_ZONE, $LOG_PATH
 * &#10004; Various system setups, not dealing well with $ENV{'home'} in multi perl environment, releases.
 
 ## Urgent FIXES and Known Issuses
 
-* Expired sessions, swallow submits into void.
+* &#10004; Expired sessions, swallow submits into void.
 * CVS Export and Import has not been implemented for RTF type log entries.
-&#10004; Dynamic toggle of page sections, interaction fixed, bettered.
-
-## New Features of Minor Relevance
-
+* &#10004; Dynamic toggle of page sections, interaction fixed, bettered.
+
+## Planned New Possible Features of Minor Relevance
+
+* Plugin subpages.
+  * Configured in main.cnf
+  * Appear on menu or as dropdouwn in the header.
+  * Downloaded/Configurated from the configuration page.
+* View save feature. Where you name and save to config or session a dropdown of views.
+* Fit to page log. Making log subpage scrollable rather than whole page to see the bottom.
+* Make session timeot sub pages aware via JSON.
+* Multiple category assignment table (set via hashtags and end of a post).
+* Log cards Export/Import. Send log entries via email or USB, why not?
+* Provide sub alias login that sets data visible to only a set of categories.
+  * View specific based login on a different password.
+* &#10004; Table sort in config system settings by variable name.
+* Enable automatic bold title heading for specified cattegories.
 * Theme colours to be revisited, bettered
 * Enable file attachment to log entries.
 * Enable Armour Mode
@@ -53,8 +86,16 @@ This version is not compatible in data structure to prior versions. Data migrati
 
 ## Bugs
 
-### v. 1.7 Encountered/Fixed
+### v. 1.8 Encountered/Fixed
 
+* &#10004; Bug 19 - Same day datediff is displaying wrong report in time stack on the page.
+* &#10004; Issue 18 - Setting excludes for views, deliveres page but long delays with server finished exchange (page doesn't hang).
+  * The page is server delivered, if sections contain external internet links, this timeouts page browser delivery if the internet is down.
+* &#10004; Bug 17 - Editing of entries on occasions, duplicates entries.
+* &#10004; Bug 16 - Saving new log entries with rtf overides previous log entries rtf.
+  * Issue 16.1 - Currently importing of records linked to rtf notes is not supported.
+* &#10004; Issue 15 Date diff, showes upside down first range by current date with multiple selections.
+  * Range should be selected from date in selected latest to current date last as inbetween difference.
 * &#10004; Issue 14 Subpages pages links to main, restart main page session counter, making the main page fully usable.
   * Not really a bug. Session will expire but time remaining will be displayed wrong on the main page.
   * All subpages need either to inherit the counter, and jump user to the login screen if expired.
@@ -77,11 +118,13 @@ This version is not compatible in data structure to prior versions. Data migrati
 * &#10004; Bug - 04, Local not picked up properly on current date.
 * &#10004; Bug - 03, Keyword search not working on words as they are categorized wrongly by other dropdown in the background.
 * &#10004; Bug - 02, Record set paging to previous page not always working. Getting stuck.
-   * This occurs on new records placed in the far past. Complex problem.
+  * This occurs on new records placed in the far past. Complex problem.
 * &#10004; Bug - 01, date validation for proper entered time, there is no 24 h.
 
 ***
 
-     Checked (&#10004;) Are items that have been done and submitted to the branch.
+   Document is from project ->  <https://github.com/wbudic/LifeLog/>
+
+   An open source application.
 
-     Project ->  <https://github.com/wbudic/LifeLog/>
+<center>Sun Stage v.1.8 - 2020</center>
index 80108d7b05d11e121fba935279432bb15e14015d..4244fe14de224656ae1fd209efc7ef0d37edac98 100644 (file)
@@ -8,7 +8,7 @@
  -- Note - Perl and some modules might take time to install
            as their fetched and tested for your computer.
 
-           
+
 
 ## Install tiny thttpd web server. ##
 
@@ -65,7 +65,7 @@ git clone https://github.com/wbudic/LifeLog.git
 
 
 
-#Install cpanm to make installing other modules easier (you'll thank us later). 
+#Install cpanm to make installing other modules easier (you'll thank us later).
 #You need to type these commands into a Terminal emulator (Mac OS X, Win32, Linux)
 
 sudo apt install cpanminus
@@ -103,26 +103,34 @@ NOTICE -> Above Perl installation and modules can take time as they build (compi
           using your computers configuration and hardware for optimal performance.
           The Perl::LanguageServer, can fail in tests, as it development specific, can be ignored.
 
+#Install OpenSSL (Optional)
+https://www.openssl.org/
+sudo apt install openssl
+
+
+# LifeLog Required Perl modules.
 
+###
+# since 1.8 switched to:
+# before was -> sudo cpanm Try::Tiny;
+sudo cpan Log::Log4per
+sudo cpan Syntax::Keyword::Try
 
-# LifeLOg Required Perl modules.
-sudo cpanm DateTime;
-sudo cpanm DateTime::Format::Human::Duration
-sudo cpanm DateTime::Format::SQLite;
-sudo cpanm Text::CSV;
-sudo cpanm Number::Bytes::Human;
-sudo cpanm CGI::Session;
-sudo cpanm Try::Tiny;
-sudo cpanm Number/Bytes/Human.pm;
-sudo cpanm Regexp::Common;
-sudo cpanm JSON;
-sudo cpanm Switch;
-sudo cpanm install IPC::Run
+sudo cpan DateTime;
+sudo cpan DateTime::Format::Human::Duration
+sudo cpan DateTime::Format::SQLite;
+sudo cpan Text::CSV;
+sudo cpan Number::Bytes::Human;
+sudo cpan CGI::Session;
+sudo cpan Number/Bytes/Human.pm;
+sudo cpan Regexp::Common;
+sudo cpan JSON;
+sudo cpan Switch;
+sudo cpan install IPC::Run
 
 #Install DBI module
-sudo cpanm DBI;
-sudo cpanm DBD::SQLite;
+sudo cpan DBI;
+sudo cpan DBD::SQLite;
 
 #Final Perl Installation Notes
 
@@ -130,7 +138,7 @@ This perl setup might take time and efforts. But, it is worth it.
 You get it build and tested professionally, based on your hardware.
 Platforms supported, Windows, Unix (all), Mac.
 
-Installing perl as an developer, requires no sudo. 
+Installing perl as an developer, requires no sudo.
 But hence can't run server (system level) like.
 
 If developer and running perlbrew, recommended is to use
@@ -145,7 +153,7 @@ cd /home/{user}/thttpd_dev/dbLifeLog
 sqlite3 -csv data_log.db "select * from LOG;" > current_log.csv
 
 ##Install LifeLog Independently
-cd /home/{user}/ 
+cd /home/{user}/
 git clone https://github.com/wbudic/LifeLog
 mkdir /home/{user}/thttpd_dev/dbLifeLog
 chmod +x /home/{user}/thttpd_dev/cgi-bin/*.cgi
@@ -157,7 +165,7 @@ Access the webserver cgi-bin. http://localhost:8080/cgi-bin/main.cgi
 (this might redirect to login.cgi or config.cgi in the future)
 
 ##Install LifeLog Dependably (not automatic, manual developer way)
-cd /home/{user}/ 
+cd /home/{user}/
 git clone https://github.com/wbudic/LifeLog
 run thttpd with:
 cd LifeLog; ./startDevWebServer.sh
@@ -168,7 +176,7 @@ Once created you must import the from above example current_log.csv
 cd /home/{user}/thttpd_dev/dbLifeLog
 see: http://www.sqlitetutorial.net/sqlite-import-csv/
 
-Example (data_dev1_2_log.db would be created as the latest version by the CGI created): 
+Example (data_dev1_2_log.db would be created as the latest version by the CGI created):
 cd /home/{user}/thttpd_dev/dbLifeLog
 sqlite3 data_dev1_2_log.db
 sqlite> .mode csv
@@ -189,7 +197,7 @@ can be installed to start on reboot.
 
 sudo cp startDevWebServer.sh  /etc/init.d/
 
-Modify the following to the path of your development environment 
+Modify the following to the path of your development environment
 where thttpd.conf file is in /etc/init.d/startDevWebServer.sh
 
 Modify line -> cd /home/will/thttpd_dev
@@ -225,7 +233,7 @@ You can export and modify your added categories via an CSV file.
 Making sure the ID first column across all entries has a unique number.
 
 #Install AUTO_LOGIN
-On a personal network or small network, you might prefere to auto login when browsing to the LifeLog, 
+On a personal network or small network, you might prefere to auto login when browsing to the LifeLog,
 instead of entering every time user name and password. It makese sense, as you are the only one using it,
 don't need that extra security.
 
index 955a80e73c4f7d6ab038810ba343770622b101e6..cc8571e37b0366c2e236b8519adcf92c37c698ae 100644 (file)
--- a/README.md
+++ b/README.md
@@ -11,12 +11,15 @@ Latest  version is **1.7 release** in **Sun** stable stage, requiring some Perl
 https://www.sqlite.org/index.html database is required to run this web application.
 
 
-## New in Life Log version 1.7 
+## New in Life Log version 1.7
+
 - Views updated, having option to exlude by category now, during the session logging.
-- New system configuration options.
+- New system configuration options. i.e. $DEBUG for some sql statements.
 - Server system based snapshot logs, on stats invocation.
 - Server indentifier on login.
+
 ## Life Log version 1.5+
+
 - Ritch Text Documents can be attached to Logs.
 - Theme support. Change the look and feel. From the congiguration page.
 - Expenses and Income totals, various new calculations.
index 5686d79a0749d4640ff5d2ecd14c8847bee179c5..a690b4c4478eb92ea750d19a4409645d496e0e5e 100644 (file)
@@ -1,11 +1,14 @@
 
+!CNF1.0
 This is the main configuration file for the LifeLog applications settings.
 https://github.com/wbudic/LifeLog
 This is an Open Source License project -> https://choosealicense.com/licenses/isc/
 Credential format:<<AUTO_LOGIN <{alias}/{password}> , dont enable here using AUTO_LOGIN option bellow, use config in app.
 <<AUTO_LOGIN</>
+# BACKUP_ENABLED -> Enable (1), disable (0) backups to be restored from config page.
+<<BACKUP_ENABLED<1>
 <<CONFIG<4>
-00|$RELEASE_VER = 1.7`LifeLog Application Version.
+00|$RELEASE_VER = 1.8`LifeLog Application Version.
 01|$REC_LIMIT   = 25`Records shown per page.
 03|$TIME_ZONE   = Australia/Sydney`Time zone of your country.
 05|$PRC_WIDTH   = 80`Default presentation width for pages.
@@ -21,6 +24,8 @@ Credential format:<<AUTO_LOGIN <{alias}/{password}> , dont enable here using AUT
 28|$THEME       = Standard`Theme to applay, Standard, Sun, Moon, Earth.
 30|$DEBUG       = 0`Development page additional debug output, off (default) or on.
 32|$KEEP_EXCS   = 0`Cache excludes between sessions, off (default) or on.
+34|$VIEW_ALL_LMT=1000`Limit of all records displayed for large logs. Set to 0, for unlimited.
+36|$TRACK_LOGINS=1`Create system logs on login/logout of Life Log.
 <<CAT<3>
 01|Unspecified `For quick uncategorised entries.
 03|File System `Operating file system/Application short log.
@@ -35,6 +40,8 @@ Credential format:<<AUTO_LOGIN <{alias}/{password}> , dont enable here using AUT
 52|Sport/Club  `Sport or Social related entry.
 55|Cars        `Car(s) related entry.
 60|Online      `Online purchases (ebay, or received/ordered from online source).
+88|Diary       `Diary specific log and entry. Your daily yaddi-yadda that have decided to place here.
+90|Fitness     `Fitness steps, news, info, and usefull links. Ammount is steps.
 <<MIG<>
 NOTES|DROP TABLE NOTES;' ver. 1.5 fts4 virtual tables have been scratched as they require special SQLite compilation.
 LOG<5>|Run Query ' ver. 1.5
@@ -79,3 +86,9 @@ UPDATE LOG SET AFLAG=2 WHERE ID_CAT=%EXPENSE_ID%;
 LOG<6>|Run Query ' ver. 1.6
 ALTER TABLE LOG ADD STICKY BOOL DEFAULT 0;
 
+LOG<6>|Run Query ' ver. 1.8
+
+CREATE TABLE notes_temp_table (LID INTEGER PRIMARY KEY NOT NULL, DOC TEXT);
+INSERT INTO notes_temp_table SELECT `LID`,`DOC` FROM `NOTES`;
+DROP TABLE `NOTES`;
+ALTER TABLE `notes_temp_table` RENAME TO `NOTES`;
index 4b0b4d8833e6904eb3006f0153d83a27aca149ea..f2d2682aa1c8253c99ce4a94ad10ab855de20784 100755 (executable)
@@ -5,28 +5,28 @@
 #
 use strict;
 use warnings;
-use Try::Tiny;
 use Switch;
 
 use CGI;
 use CGI::Session '-ip_match';
 use CGI::Carp qw ( fatalsToBrowser );
 use DBI;
+use Exception::Class ('LifeLogException');
+use Syntax::Keyword::Try;
 
 use DateTime;
 use DateTime::Format::SQLite;
 use DateTime::Duration;
 use Date::Language;
 use Text::CSV;
+use Scalar::Util qw(looks_like_number);
+use Sys::Syslog qw(:DEFAULT :standard :macros);
 
 #DEFAULT SETTINGS HERE!
 use lib "system/modules";
 require Settings;
 ##
 
-#This is the OS developer release key, replace on istallation. As it is not secure.
-my $cipher_key = '95d7a85ba891da';
-
 #15mg data post limit
 $CGI::POST_MAX = 1024 * 15000;
 my ($LOGOUT,$ERROR) = (0,"");
@@ -35,8 +35,8 @@ my $session = new CGI::Session("driver:File", $cgi, {Directory=>&Settings::logPa
 my $sid=$session->id();
 my $dbname  =$session->param('database');
 my $userid  =$session->param('alias');
-my $password=$session->param('passw');
-my $sys = `uname -n`;
+my $pass    =$session->param('passw');
+my $sys     = `uname -n`;
 #my $acumululator="";
 
 if(!$userid||!$dbname){
@@ -46,7 +46,7 @@ if(!$userid||!$dbname){
 
 my $database = &Settings::logPath.$dbname;
 my $dsn= "DBI:SQLite:dbname=$database";
-my $db = DBI->connect($dsn, $userid, $password, { RaiseError => 1 }) or die "<p>Error->"& $DBI::errstri &"</p>";
+my $db = DBI->connect($dsn, $userid, $pass, { RaiseError => 1 }) or die "<p>Error->"& $DBI::errstri &"</p>";
 
 ### Fetch settings
     Settings::getConfiguration($db);
@@ -55,20 +55,21 @@ my $db = DBI->connect($dsn, $userid, $password, { RaiseError => 1 }) or die "<p>
 
 my $rv;
 my $dbs;
-my $today  = DateTime->now;
 my $lang   = Date::Language->new(&Settings::language);
+my $today  = DateTime->now;
+   $today->set_time_zone( &Settings::timezone );
 my $tz     = $cgi->param('tz');
 my $csvp   = $cgi->param('csv');
 
 &exportToCSV if ($csvp);
 
-if($cgi->param('data_cat')){
-    &importCatCSV;
-}elsif($cgi->param('data_log')){
-    &importLogCSV;
-}
+if($cgi->param('bck'))        {&backup;}
+elsif($cgi->param('bck_del')) {&backupDelete;}
+elsif($cgi->param('data_bck')){&restore;}
+elsif($cgi->param('data_cat')){&importCatCSV;}
+elsif($cgi->param('data_log')){&importLogCSV;}
+
 
-$today->set_time_zone( &Settings::timezone );
 
 my $stmtCat = 'SELECT * FROM CAT ORDER BY ID;';
 my $status = "Ready for change!";
@@ -95,10 +96,11 @@ print qq(<div id="menu" title="To close this menu click on its heart, and wait."
 <a class="a_" href="stats.cgi">Stats</a><hr>
 <a class="a_" href="main.cgi">Log</a><hr>
 <font size="2"><b>Jump to Sections</b><br>
-<a href="#top">Categories</a><br>
+<a href="#categories">Categories</a><br>
 <a href="#vars">System</a><br>
 <a href="#dbsets">DB Fix</a><br>
-<a href="#passets">Password</a>
+<a href="#passets">Pass</a><br>
+<a href="#backup">Backup</a>
 </font>
 <hr>
 <br>
@@ -110,9 +112,9 @@ my $tbl = '<table id="cnf_cats" class="tbl" border="1" width="'.&Settings::pageP
               <tr class="r0"><td colspan="4"><b>* CATEGORIES CONFIGURATION *</b></td></tr>
             <tr class="r1"><th>ID</th><th>Category</th><th  align="left">Description</th></tr>
           ';
-$dbs = dbExecute($stmtCat);
+$dbs = Settings::selectRecords($db, $stmtCat);
 while(my @row = $dbs->fetchrow_array()) {
-    if($row[0]>0){
+    if( $row[0]>0 ){
        $tbl .= '<tr class="r0"><td>'.$row[0].'</td>
             <td><input name="nm'.$row[0].'" type="text" value="'.$row[1].'" size="12"></td>
             <td align="left"><input name="ds'.$row[0].'" type="text" value="'.$row[2].'" size="64"></td>
@@ -120,7 +122,7 @@ while(my @row = $dbs->fetchrow_array()) {
     }
  }
 
-my  $frm = qq(
+my  $frmCats = qq(
      <form id="frm_config" action="config.cgi">).$tbl.qq(
       <tr class="r1">
          <td><input type="text" name="caid" value="" size="3"/></td>
@@ -128,8 +130,10 @@ my  $frm = qq(
          <td align="left"><input type="text" name="cade" value="" size="64"/></td>
         </tr>
       <tr class="r1">
-         <td colspan="2"><a href="#bottom">&#x21A1;</a>&nbsp;&nbsp;&nbsp;<input type="submit" value="Add New Category" onclick="return submitNewCategory()"/></td>
-         <td colspan="1" align="right"><b>Categories Configuration In -> $dbname</b>&nbsp;<input type="submit" value="Change"/></td>
+         <td colspan="2"><a href="#bottom">&#x21A1;</a>&nbsp;&nbsp;&nbsp;
+         <input type="submit" value="Add New Category First" onclick="return submitNewCategory()"/> or <input type="submit" value="Change"/></td>
+         <td colspan="1" align="right"><b>Categories Configuration In -> $dbname</b>&nbsp;
+         <input type="submit" value="Change" onclick="return checkConfigCatsChange()"/></td>
         </tr>
         <tr class="r1">
           <td colspan="3"><div style="text-align:left; float"><font color="red">WARNING!</font>
@@ -141,19 +145,17 @@ my  $frm = qq(
         </tr>
         </table><input type="hidden" name="cchg" value="1"/></form><br>);
 
-
 $tbl = qq(<table id="cnf_sys" class="tbl" border="1" width=").&Settings::pagePrcWidth.qq(%">
               <tr class="r0"><td colspan="3"><b>* SYSTEM CONFIGURATION *</b></td></tr>
             <tr class="r1" align="left">
                             <th width="20%">Variable</th>
                             <th width="20%">Value</th>
-                                <th width="60%">Description</th>
+                            <th width="60%">Description <input type="submit" value="Change" style="float:right"/></th>
                         </tr>
        );
-my $stm = 'SELECT ID, NAME, VALUE, DESCRIPTION FROM CONFIG;';
-$dbs = $db->prepare( $stm );
-$rv = $dbs->execute() or die or die "<p>Error->"& $DBI::errstri &"</p>";
-
+my $stm = 'SELECT ID, NAME, VALUE, DESCRIPTION FROM CONFIG ORDER BY NAME;';
+   $dbs = Settings::selectRecords($db, $stm);
+my $REL ="";
 while(my @row = $dbs->fetchrow_array()) {
 
          my $n = $row[1]; next if($n =~ m/^\^/); #skip private tagged settings
@@ -259,7 +261,7 @@ while(my @row = $dbs->fetchrow_array()) {
                    <option$s3>Earth</option>
                 </select>);
         }
-        elsif($n eq "KEEP_EXCS"){
+        elsif($n eq "KEEP_EXCS" or $n eq 'TRACK_LOGINS' or $n eq 'DEBUG'){
             my($l,$u)=("","");
             if($v == 0){
                $l = "SELECTED"
@@ -267,30 +269,21 @@ while(my @row = $dbs->fetchrow_array()) {
             else{
                $u = "SELECTED"
             }
-            $v = qq(<select id="excs" name="var$i">
+            $v = qq(<select id="onoff" name="var$i">
                    <option value="0" $l>Off</option>
                    <option value="1" $u>On</option>
                 </select>);
         }
-        elsif($n eq "DEBUG"){
-            my($l,$u)=("","");
-            if($v == 0){
-               $l = "SELECTED"
-            }
-            else{
-               $u = "SELECTED"
-            }
-            $v = qq(<select id="dbg" name="var$i">
-                   <option value="0" $l>Off</option>
-                   <option value="1" $u>On</option>
-                </select>);
+        elsif($n eq "RELEASE_VER"){
+            $REL = qq(<td>$n</td>
+                      <td>$v</td>
+                      <td>$d</td>);
+            next;
         }
-        elsif($n ne "RELEASE_VER"){
-             $v = '<input name="var'.$i.'" type="text" value="'.$v.'" size="12">';
+        elsif(!defined(Settings::anon($n))){ #change into settable field to us found here unknown and not anon.
+            $v = '<input name="var'.$i.'" type="text" value="'.$v.'" size="12">';
         }
 
-
-
        $tbl = qq($tbl
        <tr class="r0" align="left">
             <td>$n</td>
@@ -299,14 +292,14 @@ while(my @row = $dbs->fetchrow_array()) {
         </tr>);
 }
 
+$tbl = qq($tbl<tr class="r1" align="left">$REL</tr>); #RELEASE VERSION we make to outstand last, can't be changed. :)
 
 my  $frmVars = qq(
      <form id="frm_vars" action="config.cgi">$tbl
       <tr class="r1">
          <td colspan="3" align=right><b>System Settings In -> $dbname</b>&nbsp;<input type="submit" value="Change"/></td>
         </tr>
-        <input type="hidden" name="sys" value="1"/>
-        </table></form><br>);
+        </table><input type="hidden" name="sys" value="1"/></form><br>);
 
 
 
@@ -332,33 +325,58 @@ my  $frmDB = qq(
                                  <font color="red">WARNING!</font> Checking any of the above extra actions will cause loss
                                                   of your changes. Please, export/backup first.</td>
         </tr>
-        <input type="hidden" name="db_fix" value="1"/>
-        </table></form><br>
+        </table><input type="hidden" name="db_fix" value="1"/></form><br>
         );
 $tbl = qq(<table id="cnf_fix" class="tbl" border="1" width=").&Settings::pagePrcWidth.qq(%">
-              <tr class="r0"><td colspan="2"><b>* CHANGE PASSWORD *</b></td></tr>
+              <tr class="r0"><td colspan="2"><b>* CHANGE PASS *</b></td></tr>
              );
 my  $frmPASS = qq(
      <form id="frm_PASS" action="config.cgi">$tbl
-        <tr class="r1" align="left"><td style="width:100px">Existing:</td><td><input type="password" name="existing" value="" size="12"/></td></tr>
-        <tr class="r1" align="left"><td>New:</td><td><input type="password" name="new" value="" size="12"/></td></tr>
-        <tr class="r1" align="left"><td>Confirmation:</td><td><input type="password" name="confirm" value="" size="12"/></td></tr>
+        <tr class="r1" align="left"><td style="width:100px">Existing:</td><td><input type="pass" name="existing" value="" size="12"/></td></tr>
+        <tr class="r1" align="left"><td>New:</td><td><input type="pass" name="new" value="" size="12"/></td></tr>
+        <tr class="r1" align="left"><td>Confirmation:</td><td><input type="pass" name="confirm" value="" size="12"/></td></tr>
         <tr class="r1">
-         <td colspan="2" align="right"><b>Password change for -> $userid</b>&nbsp;<input type="submit" value="Change"/></td>
+         <td colspan="2" align="right"><b>Pass change for -> $userid</b>&nbsp;<input type="submit" value="Change"/></td>
         </tr>
-        <input type="hidden" name="pass_change" value="1"/>
-        </table></form><br>
+        </table><input type="hidden" name="pass_change" value="1"/></form><br>
         );
 
 
+my @backups = ();
+my ($file,$bck_list) ="";
+opendir my $dir, &Settings::logPath;
+while($file = readdir $dir){
+next if $file eq '.' or $file eq '..' or index ($file , 'bck_') == -1;
+  push @backups, $file;
+}
+close $dir;
+foreach $file (sort @backups){
+    #my $n = substr $file, length(&Settings::logPath);
+    $bck_list .=  "<input name='bck_file' type='radio' value='$file'>$file</input><br>";
+}
+if(length $bck_list == 0){
+$bck_list = '<p>Restore will bring back and merge log entries from the time of backup.</p>';
+}
+else{
+    $bck_list = qq(<p>Tick Select Backup to Restore or Delete</p><p>$bck_list</p>);
+}
+
+my $inpRestore = qq(<input type="button" onclick="return deleteBackup();" value="Delete"/>
+<input type="file" name="data_bck" />&nbsp;&nbsp;<input type="Submit" onclick="return true;restoreBackup();" value="Restore"/>);
+my $inpCVS = qq(<input type="button" onclick="return exportToCSV('log',0);" value="Export"/>&nbsp;
+<input type="button" onclick="return exportToCSV('log',1);" value="View"/>);
+if((Settings::anon("backup_enabled") == 0)){
+    $inpRestore = $inpCVS = '&nbsp;<i><b>Sorry this feature has been dissabled!</b></i>';
+}
+
 #
-#Page printout from here!
+#  Page printout from here!
 #
 
 print qq(
 <a name="top"></a><center>
-    <div>$frm</div>
     <div><a name="vars"></a>$frmVars</div>
+    <div><a name="categories"></a>$frmCats</div>
     <div><a name="dbsets"></a>$frmDB</div>
     <div><a name="passets"></a>$frmPASS</div>
     <div id="rz" style="text-align:center;width:).&Settings::pagePrcWidth.qq(%;">
@@ -366,28 +384,36 @@ print qq(
     </div>
     <br>
     <div id="rz" style="text-align:left; width:640px; padding:10px; background-color:).&Settings::bgcol.qq(">
+            <form id="bck" action="config.cgi" method="post" enctype="multipart/form-data">
             <table border="0" width="100%">
+                <tr><td><a name="backup"></a><H3>Backup File Format</H3></td></tr>
+                <tr><td><input type="button" onclick="return fetchBackup();" value="Fetch"/><hr></td></tr>
+
+                <tr><td><div id="div_backups">$bck_list</div><hr></td></tr>
+                <tr><td>
+                $inpRestore
+                <hr></td></tr>
+
                 <tr><td><H3>CSV File Format</H3></td></tr>
-                <form action="config.cgi" method="post" enctype="multipart/form-data">
+
                 <tr style="border-left: 1px solid black;"><td>
                         <b>Import Categories</b>: <input type="file" name="data_cat" /></td></tr>
                 <tr style="border-left: 1px solid black;"><td style="text-align:right;">
                         <input type="submit" name="Submit" value="Submit"/></td>
                 </tr>
+
                 </form>
+                <form action="config.cgi" method="post" enctype="multipart/form-data">
                 <tr><td><b>Export Categories:</b>
-                                           <input type="button" onclick="return exportToCSV('cat',0);" value="Export"/>&nbsp;
-                                           <input type="button" onclick="return exportToCSV('cat',1);" value="View"/>
+                       <input type="button" onclick="return exportToCSV('cat',0);" value="Export"/>&nbsp;
+                       <input type="button" onclick="return exportToCSV('cat',1);" value="View"/>
                 </td></tr>
-                <form action="config.cgi" method="post" enctype="multipart/form-data">
                 <tr style="border-top: 1px solid black;border-right: 1px solid black;"><td>
                         <b>Import Log</b>: <input type="file" name="data_log" /></td></tr>
                 <tr style="border-right: 1px solid black;"><td style="text-align:right;">
                         <input type="submit" name="Submit" value="Submit"/></td></tr>
                 </form>
-                <tr><td><b>Export Log:</b>
-                                           <input type="button" onclick="return exportToCSV('log',0);" value="Export"/>&nbsp;
-                                           <input type="button" onclick="return exportToCSV('log',1);" value="View"/>
+                <tr><td><b>Export Log:</b>$inpCVS
                 </td></tr>
                 <tr><td style="text-align:right"><H3>For Server -> $sys -> $dbname</H3></td></tr>
             </table><br><a href="#top">&#x219F;&nbsp;Go to Top of page</a>
@@ -401,22 +427,22 @@ print qq(
                     for your logs HTML layout.
                     </p>
                     <p>
-                    <b>&#60;&#60;B&#60;<i>{Text To Bold}</i><b>&#62;</b>
+                    <b>&#60;&#60;B&#60;<i>{Text To Bold}</i><b>&#62;&#62;</b>
                     </p>
                     <p>
-                    <b>&#60;&#60;I&#60;<i>{Text To Italic}</i><b>&#62;</b>
+                    <b>&#60;&#60;I&#60;<i>{Text To Italic}</i><b>&#62;&#62;</b>
                     </p>
                     <p>
-                    <b>&#60;&#60;TITLE&#60;<i>{Title Text}</i><b>&#62;</b>
+                    <b>&#60;&#60;TITLE&#60;<i>{Title Text}</i><b>&#62;&#62;</b>
                     </p>
                     <p>
-                    <b>&#60;&#60;LIST&#60;<i>{List of items delimited by new line to terminate item or with '~' otherwise.}</i><b>&#62;</b>
+                    <b>&#60;&#60;LIST&#60;<i>{List of items delimited by new line to terminate item or with '~' otherwise.}</i><b>&#62;&#62;</b>
                     </p>
                     <p>
-                    <b>&#60;&#60;IMG&#60;<i>{url to image}</i><b>&#62;</b>
+                    <b>&#60;&#60;IMG&#60;<i>{url to image}</i><b>&#62;&#62;</b>
                     </p>
                     <p>
-                        <b>&#60;&#60;FRM&#60;<i>{file name}_frm.png}</i><b>&#62;</b><br><br>
+                        <b>&#60;&#60;FRM&#60;<i>{file name}_frm.png}</i><b>&#62;&#62;</b><br><br>
                         *_frm.png images file pairs are located in the ./images folder of the cgi-bin directory.<br>
                         These are manually resized by the user. Next to the original.
                         Otherwise considered as stand alone icons. *_frm.png Image resized to ->  width="210" height="120"
@@ -428,22 +454,20 @@ print qq(
 
           For log entry, place:
 
-      &#60;&#60;FRM&#62;my_cat_simon_frm.png&#62; &#60;&#60;TITLE&#60;Simon The Cat&#62;
+      &#60;&#60;FRM&#62;my_cat_simon_frm.png&#62; &#60;&#60;TITLE&#60;Simon The Cat&#62;&#62;
       This is my pet, can you hold him for a week while I am on holiday?
-            </pre>
+                        </pre>
                     </p>
-                    <p>
-                    <b>&#60;&#60;LNK&#60;<i>{url to image}</i><b>&#62;</b><br><br>
+
+                    <p><b>&#60;&#60;LNK&#60;<i>{url to image}</i><b>&#62;&#62;</b><br><br></p>
+                     <p>
                     Explicitly tag an URL in the log entry.
                     Required if using in log IMG or FRM tags.
                     Otherwise link appears as plain text.
                     </p>
                     <hr>
-          </p>
                         <h3>Log Page Particulars</h3>
                         &#x219F; or &#x21A1; - Jump links to top or bottom of page respectivelly.
-                    </p>
-                    </div>
                     </center><a name="bottom"></a><a href="#top">&#x219F;</a>
                     <hr>
 </div>
@@ -507,26 +531,30 @@ my $del_date_from = $cgi->param("date_from");
 my ($s, $d);
 
 try{
-
-$dbs = $db->prepare( $stmtCat );
-$rv = $dbs->execute() or die "<p>Error->"& $DBI::errstri &"</p>";
-
+$dbs = Settings::selectRecords($db, $stmtCat );
 if($passch){
     my ($ex,$ne,$cf) = ($cgi->param("existing"),$cgi->param("new"),$cgi->param("confirm"));
     if($ne ne $cf){
-         $status = "New password must match confirmation!";
+         $status = "New pass must match confirmation!";
          print "<center><div><p><font color=red>Client Error</font>: $status</p></div></center>";
     }
     else{
-        if(&confirmExistingPassword($ex)){
-             &changePassword($ne);
-             $status = "Password Has Been Changed";
+        if(&confirmExistingPass($ex)){
+             &changePass($ne);
+             $status = "Pass Has Been Changed";
         }
         else{
-            $status = "Wrong existing password was entered, are you user by alias: $userid ?";
+            $status = "Wrong existing pass was entered, are you user by alias: $userid ?";
             print "<center><div><p><font color=red>Client Error</font>: $status</p></div></center>";
         }
     }
+
+    openlog($dsn, 'cons,pid', "user");
+        syslog('info', 'Status:%s', $status);
+        syslog('info', 'Password change request for %s', $$userid);
+    closelog();
+
+
 }
 elsif ($change == 1){
 
@@ -548,7 +576,7 @@ elsif ($change == 1){
            $d->execute();
 
             while(my @r = $d->fetchrow_array()) {
-                     $s = "  LOG SET ID_CAT=1 WHERE rowid=".$r[0].";";
+                     $s = "UPDATE LOG SET ID_CAT=1 WHERE rowid=".$r[0].";";
                      $d = $db->prepare($s);
                      $d->execute();
              }
@@ -572,7 +600,6 @@ elsif ($change == 1){
 
 if($change > 1){
 
-
     my $caid  = $cgi->param('caid');
     my $canm  = $cgi->param('canm');
     my $cade  = $cgi->param('cade');
@@ -615,57 +642,62 @@ elsif($chdbfix){
 
     if( $isByCat || $isByDate){
 
-        my $output = qq(<form id="frm_log" action="remove.cgi" onSubmit="return formDelValidation();">
+        my $output = qq(<a name="top"></a><form id="frm_log" action="data.cgi" onSubmit="return formDelValidation();">
                     <TABLE class="tbl" border="0" width=").&Settings::pagePrcWidth.qq(%">
-                    <tr class="hdr"><td colspan="5"><h2>Select Categories To Delete</h2></td></tr>
-                    <tr class="r0">
-                        <th>Date</th>
+                    <tr class="hdr"><td colspan="5" class="r1"><h3>Select Categories To Delete</h3></td></tr>
+                    <tr class="r2">
+                        <th><a id="to_bottom" href="#bottom" title="Go to bottom of page."><span class="ui-icon ui-icon-arrowthick-1-s" style="float:none;"></span></a> Date</th>
                         <th>Time</th>
                         <th>Log</th><th>#</th>
                         <th>Category</th>
                     </tr>);
         my $sel ="";
-        if ($isByCat){$sel = "ID_CAT ='$category'"}
+        if($isByCat){$sel = "ID_CAT ='$category'"}
         if($isByDate){
-            if ($isByCat){ $sel .= " AND ";}
+            $sel .= " AND " if ($isByCat);
             $sel .= "DATE<='$del_date_from'";
         }
 
 
-       $dbs = $db->prepare( "SELECT rowid, ID_CAT, DATE, LOG FROM LOG WHERE $sel ORDER BY DATE;" );
-       $rv = $dbs->execute() or die "<p>Error->"& $DBI::errstri &"</p>";
+       $dbs = Settings::selectRecords($db, "SELECT rowid, ID_CAT, DATE, LOG FROM LOG WHERE $sel ORDER BY DATE;" );
        while(my @row = $dbs->fetchrow_array()) {
         my $id = $row[0];# rowid
-        my $ct  = $hshCats{$row[1]}; #ID_CAT
-        my $dt  = DateTime::Format::SQLite->parse_datetime( $row[2] );
-        my $log = $row[3];
-
-        my ( $dty, $dtf ) = $dt->ymd;
-        my $dth = $dt->hms;
-        if ( &Settings::universalDate == 1 ) {
-            $dtf = $dty;
-        }
-        else {
-            $dtf = $lang->time2str( "%d %b %Y", $dt->epoch, &Settings::timezone );
-        }
+            my $ct  = $hshCats{$row[1]}; #ID_CAT
+            my $dt  = DateTime::Format::SQLite->parse_datetime( $row[2] );
+            my $log = $row[3];
+            my ( $dty, $dtf ) = $dt->ymd;
+            my $dth = $dt->hms;
+            if ( &Settings::universalDate == 1 ) {
+                $dtf = $dty;
+            }
+            else {
+                $dtf = $lang->time2str( "%d %b %Y", $dt->epoch, &Settings::timezone );
+            }
 
-        $output .= qq(<tr class="r0">
-                <td width="15%">$dtf<input id="y$id" type="hidden" value="$dty"/></td>
-                <td id="t$id" width="10%" class="tbl">$dth</td>
-                <td id="v$id" class="log" width="40%">$log</td>
-                <td id="c$id" width="10%" class="tbl">$ct</td>
-                <td width="20%">
-                    <input name="chk" type="checkbox" value="$id"/>
-                </td></tr>);
+                $log =~ s/''/'/g;
+                $log =~ s/\\n/<br>/gs;
+
+            $output .= qq(<tr class="r2">
+                    <td width="15%">$dtf<input id="y$id" type="hidden" value="$dty"/></td>
+                    <td id="t$id" width="10%" class="tbl">$dth</td>
+                    <td id="v$id" class="log" width="40%">$log</td>
+                    <td id="c$id" width="10%" class="tbl">$ct</td>
+                    <td width="20%">
+                        <input name="chk" type="checkbox" value="$id"/>
+                    </td></tr>);
        }#while
-       $output .= qq(<td colspan="5" align="right">
-        <button onclick="return selectAllLogs()">Select All</button>
+       $output .= qq(<tr class="r3"><td style="text-align:left;">
+       <a id="to_top" href="#top" title="Go to top of page.">To Top<span class="ui-icon ui-icon-arrowthick-1-n" style="float:none;"></span></a>
+       <a href="config.cgi?CGISESSID=$sid#dbsets">Go Back</a></td>
+       <td colspan="4" align="right">
+        <a name="bottom"></a><button onclick="return selectAllLogs()">Select All</button>
         <input type="reset" value="Unselect All"/>
+        <input type="hidden" name="confirmed" value="1">
         <input id="del_sel" type="submit" value="Delete Selected"/>
         </td></tr>
         </form></TABLE>);
 
-        &getTheme;
+        &Settings::getTheme;
         &getHeader;
 
         print "<div>$output</div>";
@@ -684,33 +716,46 @@ elsif($chdbfix){
 
 }
 catch{
-    $ERROR = qq(<p><font color=red><b>SERVER ERROR</b></font> -> $_</p>);
+
+        my $err = $@;
+        my $pwd = `pwd`;
+           $pwd =~ s/\s*$//;
+
+        $ERROR =
+        "<hr><font color=red><b>SERVER ERROR</b></font> on ".DateTime->now.
+        "<hr><pre>".$pwd."/$0 -> &".caller." -> [$err]","</pre>",
+
+
+
 }
+
+    openlog($dsn, 'cons,pid', "user");
+        syslog('info', 'Status:%s', $status);
+        syslog('err', '%s', $ERROR) if ($ERROR);
+    closelog();
 }
 
-sub confirmExistingPassword {
+sub confirmExistingPass {
         my $pass = $_[0];
-      my $crypt = encryptPassw($pass);
+        my $crypt = encryptPassw($pass);
         my $sql = "SELECT ALIAS, PASSW from AUTH WHERE ALIAS='$userid' AND PASSW='$crypt';";
     #          print "<center><div><p><font color=red><b>DEBUG</b></font>:[$pass]<br>$sql</p></div></center>";
-        $dbs = $db->prepare($sql);
-        $dbs->execute();
+        $dbs = Settings::selectRecords($db, $stmtCat );
         if($dbs->fetchrow_array()){
             return 1;
         }
         return 0;
 }
-sub changePassword {
+sub changePass {
       my $pass = encryptPassw($_[0]);
-        $dbs = $db->prepare("UPDATE AUTH SET PASSW='$pass' WHERE ALIAS='$userid';");
-        $dbs->execute();
+        $dbs = Settings::selectRecords($db, "UPDATE AUTH SET PASSW='$pass' WHERE ALIAS='$userid';");
         if($dbs->fetchrow_array()){
             return 1;
         }
         return 0;
 }
 sub encryptPassw {
-    return uc crypt $_[0], hex $cipher_key;
+    return uc crypt $_[0], hex Settings->CIPHER_KEY;
 }
 
 
@@ -738,7 +783,7 @@ try{
 
         $db->do('BEGIN TRANSACTION;');
         #Check for duplicates, which are possible during imports or migration as internal rowid is not primary in log.
-        $dbs = dbExecute('SELECT rowid, DATE FROM LOG ORDER BY DATE;');
+        $dbs = Settings::selectRecords($db, 'SELECT rowid, DATE FROM LOG ORDER BY DATE;');
         while(@row = $dbs->fetchrow_array()) {
             my $existing = $dates{$row[0]};
             if($existing && $existing eq $row[1]){
@@ -767,7 +812,7 @@ try{
 
         $db->do('COMMIT;');
         $db->disconnect();
-        $db = DBI->connect($dsn, $userid, $password, { RaiseError => 1 }) or die "<p>Error->"& $DBI::errstri &"</p>";
+        $db = DBI->connect($dsn, $userid, $pass, { RaiseError => 1 }) or LifeLogException->throw($DBI::errstri);
         $dbs = $db->do("VACUUM;");
 
 
@@ -779,7 +824,7 @@ try{
 }
 catch{
     $db->do('ROLLBACK;');
-    die qq(@&processDBFix error -> $_ with statement->$sql for $date update counter:$cntr_upd);
+    LifeLogException->throw(error=>qq(@&processDBFix error -> $_ with statement->[$sql] for $date update counter:$cntr_upd \nERROR->$@),show_trace=>1);
 }
 }
 
@@ -827,14 +872,14 @@ try{
                             $err .= "UID{$id} taken by $vars{$id}-> $line\n";
                                                                 }
                                                                 else{
-                                                                    $dbs = dbExecute(
+                                                                    $dbs = Settings::selectRecords($db,
                                                                         "SELECT ID, NAME, VALUE, DESCRIPTION FROM CONFIG WHERE NAME LIKE '$name';");
                                                                     $inData = 1;
                                                                     my @row = $dbs->fetchrow_array();
                                                                     if(scalar @row == 0){
                                                                        #The id in config file has precedence to the one in the db,
                                                                        #from a ppossible previous version.
-                                                                       $dbs = dbExecute("SELECT ID FROM CONFIG WHERE ID = $id;");
+                                                                       $dbs = Settings::selectRecords($db, "SELECT ID FROM CONFIG WHERE ID = $id;");
                                                                        @row = $dbs->fetchrow_array();
                                                                        if(scalar @row == 0){
                                                                             $insert->execute($id,$name,$value,$tick[1]);
@@ -887,43 +932,166 @@ sub logout {
 }
 
 sub changeSystemSettings {
-    try{
-            my $updated;
-            $dbs = dbExecute("SELECT ID, NAME FROM CONFIG;");
-            while (my @r=$dbs->fetchrow_array()){
-                my $var = $cgi->param('var'.$r[0]);
-                if(defined $var){
-                    updCnf($r[0],$var);
-                    $updated = 1;
-                }
-            }
-            Settings::getConfiguration($db) if($updated);
+    my $updated;
+    $dbs = Settings::selectRecords($db, "SELECT ID, NAME FROM CONFIG;");
+    while (my @r=$dbs->fetchrow_array()){
+        my $var = $cgi->param('var'.$r[0]);
+        if(defined $var){
+            Settings::configProperty($db, $r[0], undef, $var);
+            $updated = 1;
+        }
     }
-    catch{
-        print "<font color=red><b>SERVER ERROR->changeSystemSettings</b></font>:".$_;
+    Settings::getConfiguration($db) if($updated);
+}
+
+
+sub backupDelete {
+    my $n = $cgi->param('bck_del');
+    my $f = &Settings::logPath.$n;
+try{
+    if (-e $f) {
+         LifeLogException->throw("File -> <i>[$n]</i> is not a backup file or it doesn't belong to $userid (you)!") if(index ($file , /bck_\d+$userid\_log/) == -1 );
+         unlink($f) or LifeLogException->throw("Failed to delete $n! -> $!");
+         print $cgi->redirect("config.cgi?CGISESSID=$sid");
+    exit;
+    } else {
+        LifeLogException->throw( "File $n does not exist!");
     }
+}catch{
+        my $err = $@;
+        &getHeader;
+        print $cgi->start_html;
+        print qq(<div class=r0><b>Delete Has Failed!<br>[$err]</div>
+                 <div class=r2><a href="config.cgi?CGISESSID=$sid"><br>Go Back</a> or <a href="main.cgi"><br>Go to main LOG</a></div>
+        );
+        print $cgi->end_html;
+        exit;
+};
 }
+sub backup {
 
-sub updCnf {
-    my ($id, $val, $s) = @_;
-    $s = "UPDATE CONFIG SET VALUE='".$val."' WHERE ID=".$id.";";
+   my $ball = 'bck__'.$today->strftime('%Y%m%d%H%M%S_')."$dbname.osz";
+   my $pipe = "tar czf - ".&Settings::logPath.'main.cnf' ." $database | openssl enc -k $pass:$userid -e -des-ede3-cfb -out ".Settings::logPath().$ball." 2>/dev/null";
+   my $rez = `$pipe`;
+
+    #print $cgi->header;
+    #print $cgi->start_html;
+    print $cgi->header(-charset=>"UTF-8", -type=>"application/octet-stream", -attachment=>$ball);
+    open (TAR, "<".Settings::logPath().$ball) or die "Failed creating backup -> $ball";
+    while(<TAR>){print $_;}
+    close TAR;
+
+    #print $cgi->end_html;
+    exit;
+
+}
+
+
+sub restore {
+
+    my $hndl = $cgi->upload("data_bck");
+    my ($pipe,@br);
     try{
-          dbExecute($s);
+
+
+        &getHeader;
+        print $cgi->start_html;
+        print "<pre>Reading->$hndl</pre>";
+        my $dbck = &Settings::logPath."bck/"; `mkdir $dbck` if (!-d $dbck);
+        my $tar = $dbck.$hndl; $tar =~ s/osz$/tar/;
+        my $pipe;
+        open ($pipe,  "| openssl enc -k $pass:$userid -d -des-ede3-cfb -in /dev/stdin 2>/dev/null > $tar"); #| tar zt");#1>/dev/null");
+            while(<$hndl>){print $pipe $_;};
+        close $pipe;
+
+        print "<pre>\n";
+        my $m1 = "it is not permitted to restore another aliases log backup.";
+        $m1= "has your log password changed?" if ($tar=~/_data_$userid/);
+
+        my $cmd = `tar tvf $tar 2>/dev/null`  or die qq(, possible an security issue, $m1\nFAILED READING $tar. \nYour alias is: <b>$userid</b>.\n);
+
+        print "Contents->".$cmd."\n\n";
+        $cmd = `tar xzvf $tar -C $dbck --strip-components 1 2>/dev/null` or die "Failed extracting $tar";
+        print "Extracted->\n".$cmd."\n" or die "Failed extracting $tar";;
+
+        my $b_base = $dbck.$dbname;
+        my $dsn= "DBI:SQLite:dbname=$b_base";
+        my $b_db = DBI->connect($dsn, $userid, $pass, { RaiseError => 1 }) or LifeLogException->throw(error=>"Invalid database! $dsn->$hndl [$@]", show_trace=>&Settings::debug);
+        print "Connected to -> $dsn\n";
+
+        print "Merging from backup categories table...\n";
+        my $insCAT   = $db->prepare('INSERT INTO CAT (ID, NAME, DESCRIPTION) VALUES(?,?,?);') or die "Failed CAT prepare.";
+
+        my $b_pst = Settings::selectRecords($b_db,'SELECT ID, NAME, DESCRIPTION FROM CAT;');
+        while ( @br = $b_pst->fetchrow_array() ) {
+            my $pst = Settings::selectRecords($db, "SELECT ID,NAME,DESCRIPTION FROM CAT WHERE ID='".$br[0]."';");
+            my @ext = $pst->fetchrow_array();
+            if(scalar(@ext)==0){
+                $insCAT->execute($br[0],$br[1],$br[2]);
+                print "Added CAT->".$br[0]."|".$br[1]."\n";
+            }
+            elsif($br[0] ne $ext[0] or $br[1] ne $ext[1]){
+                $db->do("UPDATE CAT SET NAME='".$br[1]."', DESCRIPTION='".$br[2]."' WHERE ID=?;") or die "Cat update failed!";
+                print "Updated->".$br[0]."|".$br[1]."|".$br[2]."\n";
+            }
+
+        }
+        print "\nFinished with merging CAT table.\n";
+
+        print "\n\nMerging from backup LOG table...\n";
+        my $insLOG   = $db->prepare('INSERT INTO LOG (ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY) VALUES(?,?,?,?,?,?,?);')or die "Failed LOG prepare.";
+
+        $b_pst = Settings::selectRecords($b_db,'SELECT ID, ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY FROM VW_LOG;');
+        while ( @br = $b_pst->fetchrow_array() ) {
+            my $pst = Settings::selectRecords($db,"SELECT DATE FROM VW_LOG WHERE DATE='".$br[3]."';");
+            my @ext = $pst->fetchrow_array();
+            if(scalar(@ext)==0){
+                $insLOG->execute($br[1],$br[2],$br[3],$br[4],$br[5],$br[6],$br[7]);
+                print "Added->".$br[0]."|".$br[3]."|".$br[4]."\n";
+            }
+
+        }
+        print "\nFinished with merging LOG table.\n";
+
+        print "\n\nMerging from backup NOTES table...\n";
+        my $insNOTES   = $db->prepare('INSERT INTO NOTES (LID, DOC) VALUES(?,?);')or die "Failed NOTESprepare.";
+        $b_pst = Settings::selectRecords($b_db,'SELECT LID, DOC FROM NOTES;');
+        while ( @br = $b_pst->fetchrow_array() ) {
+            my $pst = Settings::selectRecords($db,"SELECT LID FROM NOTES WHERE LID=".$br[0].";");
+            my @ext = $pst->fetchrow_array();
+            if(@ext==0&&$br[0]&&$br[1]){
+                $insNOTES->execute($br[0], $br[1]) or die "Failed NOTES INSERT[".$br[0]."]";
+                print "Added NOTES->".$br[0]."\n";
+            }
+
+        }
+        print "\nFinished with merging NOTES table.\n";
+
+        $b_db->disconnect();
+        $db->disconnect();
+        print "Done!";
     }
     catch{
-        print "<font color=red><b>SERVER ERROR</b>->updCnf[$s]</font>:".$_;
-    }
-}
+        $ERROR = "<font color='red'><b>Restore failed!</b></font> hndl->$hndl $@ \nbr:[@br]";#,show_trace=>&Settings::debug);
+    };
 
+    my $back = $cgi->url( -relative => 1 );
+    print $ERROR if($ERROR);
+    print "\n</pre><code>";
+    print qq(<a href="config.cgi?CGISESSID=$sid"><hr>Go Back</a> or <a href="main.cgi"><brr>Go to LOG</a></code>);
+    print $cgi->end_html;
+       exit;
+
+}
 
 sub exportToCSV {
     try{
         my $csv = Text::CSV->new ( { binary => 1, strict => 1,eol => $/ } );
         if($csvp > 2){
-           $dbs = dbExecute("SELECT ID, NAME, DESCRIPTION FROM CAT ORDER BY ID;");
+           $dbs = Settings::selectRecords($db, "SELECT ID, NAME, DESCRIPTION FROM CAT ORDER BY ID;");
         }
         else{
-           $dbs = dbExecute("SELECT * FROM LOG;");
+           $dbs = Settings::selectRecords($db, "SELECT * FROM LOG;");
         }
 
         if($csvp==2 || $csvp==4){
@@ -954,29 +1122,33 @@ sub exportToCSV {
 
 sub importCatCSV {
     my $hndl = $cgi->upload("data_cat");
-    my $csv = Text::CSV->new ( { binary => 1, strict => 1, eol => $/ } );
-    while (my $line = <$hndl>) {
-        chomp $line;
-        if ($csv->parse($line)) {
-              my @flds   = $csv->fields();
-            updateCATDB(@flds);
-        }else{
-              warn "Data could not be parsed: $line\n";
-          }
+    my $csv; try{
+       $csv = Text::CSV->new ( { binary => 1, strict => 1, eol => $/ } );
+        while (my $line = <$hndl>) {
+            chomp $line;
+            if ($csv->parse($line)) {
+                my @fields   = $csv->fields();
+                updateCATDB(@fields);
+            }else{
+                warn "Data could not be parsed: $line\n";
+            }
+        }
     }
+    catch{
+        LifeLogException->throw(error=>"Category update failed! CSV_STATUS->".$csv->error_diag()."\nfile_hndl->$hndl",show_trace=>&Settings::debug);
+    };
 }
 
 sub updateCATDB {
-    my @flds = @_;
-    if(@flds>2){
-    try{
-            my $id   = $flds[0];
-            my $name = $flds[1];
-            my $desc = $flds[2];
+    my @fields = @_;
+    if(@fields>2){
+            my $id   = $fields[0];
+            my $name = $fields[1];
+            my $desc = $fields[2];
 
             #is it existing entry?
-            $dbs = dbExecute("SELECT ID, NAME, DESCRIPTION FROM CAT WHERE ID = '$id';");
-            if(not defined $dbs->fetchrow_array()){
+            $dbs = Settings::selectRecords($db, "SELECT ID FROM CAT WHERE ID = '$id';");
+            if(!$dbs->fetchrow_array()){
                     $dbs = $db->prepare('INSERT INTO CAT VALUES (?,?,?)');
                     $dbs->execute($id, $name, $desc);
                     $dbs->finish;
@@ -984,69 +1156,84 @@ sub updateCATDB {
             else{
                 #TODO Update
             }
-
-    }
-    catch{
-        print "<font color=red><b>SERVER ERROR</b>->updateCATDB</font>:".$_;
     }
+      else{
+         LifeLogException->throw("Invalid CSV data format!");
     }
 }
 
 sub importLogCSV {
     my $hndl = $cgi->upload("data_log");
-    my $csv = Text::CSV->new ( { binary => 1, strict => 1, eol => $/ } );
+    my $csv;
+    try{
+
+    $csv = Text::CSV->new ( { binary => 1, strict => 1, eol => $/ } );
+
+        while (my $line = <$hndl>) {
+                chomp $line;
+                if ($csv->parse($line)) {
+                    my @fields   = $csv->fields();
+                    updateLOGDB(@fields);
+                }else{
+                        warn "Data could not be parsed: $line\n";
+                }
+        }
+        &renumerate;
+        $db->disconnect();
+        print $cgi->redirect('main.cgi');
 
-    while (my $line = <$hndl>) {
-            chomp $line;
-            if ($csv->parse($line)) {
-                  my @flds   = $csv->fields();
-                updateLOGDB(@flds);
-            }else{
-                     warn "Data could not be parsed: $line\n";
-            }
     }
-    &renumerate;
-    $db->disconnect();
-    print $cgi->redirect('main.cgi');
+    catch{
+        LifeLogException->throw(error=>"Log update failed! CSV_STATUS->".$csv->error_diag()."\nfile_hndl->$hndl",show_trace=>&Settings::debug);
+    };
     exit;
 }
 
 sub updateLOGDB {
-    my @flds = @_;
-    if(@flds>3){
-    try{
-            my $id_cat = $flds[0];
-            my $date   = $flds[1];
-            my $log    = $flds[2];
-            my $amv    = $flds[3];
-            my $amf    = $flds[4];
-            my $rtf    = $flds[5];
-            my $sticky = $flds[6];
-            my $pdate = DateTime::Format::SQLite->parse_datetime($date);
-            #Check if valid date log entry?
-            if($id_cat==0||$id_cat==""||!$pdate){
-                return;
-            }
-            #is it existing entry?
-            my $sql = "SELECT DATE FROM LOG WHERE DATE is '$pdate';";
-            $dbs = $db->prepare($sql);
-            $dbs->execute();
-            my @rows = $dbs->fetchrow_array();
-            if(scalar @rows == 0){
-                      $dbs = $db->prepare('INSERT INTO LOG VALUES (?,?,?,?,?,?,?)');
-                      $dbs->execute( $id_cat, $pdate, $log, $amv, $amf, $rtf, $sticky);
-            }
-            $dbs->finish();
-    }
-    catch{
-        print "<font color=red><b>SERVER ERROR</b>->exportLogToCSV</font>:".$_;
+    my @fields = @_;
+    if(scalar(@fields)>6){
+
+        my $i = 0;
+        my $id_cat = $fields[$i++];
+        my $id_rtf = $fields[$i++];
+        my $date   = $fields[$i++];
+        my $log    = $fields[$i++];
+        my $amv    = $fields[$i++];
+        my $amf    = $fields[$i++];
+        my $sticky = $fields[$i++];
+        # Is it old pre. 1.8 format -> ID, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY
+        if(!looks_like_number($id_rtf)){
+            $i = 0;
+            $id_cat = $fields[$i++];
+            $date   = $fields[$i++];
+            $log    = $fields[$i++];
+            $amv    = $fields[$i++];
+            $amf    = $fields[$i++];
+            $id_rtf = $fields[$i++];
+            $sticky = $fields[$i++];
+        }
+        my $pdate = DateTime::Format::SQLite->parse_datetime($date);
+        #Check if valid date log entry?
+        if($id_cat==0||$id_cat==""||!$pdate){
+            return;
+        }
+        #is it existing entry?
+        $dbs = Settings::selectRecords($db,"SELECT DATE FROM LOG WHERE DATE is '$pdate';");
+        my @rows = $dbs->fetchrow_array();
+        if(scalar @rows == 0){
+                    $dbs = $db->prepare('INSERT INTO LOG VALUES (?,?,?,?,?,?,?)');
+                    $dbs->execute($id_cat, $id_rtf, $pdate, $log, $amv, $amf, $sticky);
+        }
+        $dbs->finish();
     }
+    else{
+         LifeLogException->throw("Invalid CSV data format!");
     }
 }
 
 sub cats {
         $cats = qq(<select id="cats" name="cats"><option value="0">---</option>\n);
-        $dbs = dbExecute("SELECT ID, NAME FROM CAT ORDER BY ID;");
+        $dbs = Settings::selectRecords($db, "SELECT ID, NAME FROM CAT ORDER BY ID;");
         while ( my @row = $dbs->fetchrow_array() ) {
                 $cats .= qq(<option value="$row[0]">$row[1]</option>\n);
                 $hshCats{ $row[0] } = $row[1];
@@ -1054,14 +1241,9 @@ sub cats {
         $cats .= '</select>';
 }
 
-sub dbExecute {
-    my $ret    = $db->prepare(shift);
-       $ret->execute() or die "<p>ERROR->"& $DBI::errstri &"</p>";
-    return $ret;
-}
 
 sub error {
-    my $url = $cgi->url();
+    my $url = $cgi->url(-path_info => 1);
     print qq(<h2>Sorry Encountered Errors</h2><p>Page -> $url</p><p>$ERROR</p>);
     print qq(<h3>CGI Parameters</h3>);
     print "<ol>\n";
@@ -1079,41 +1261,34 @@ sub error {
 sub renumerate {
     #Renumerate Log! Copy into temp. table.
     my $sql;
-    $dbs = dbExecute("CREATE TABLE life_log_temp_table AS SELECT * FROM LOG;");
-    $dbs = dbExecute('SELECT rowid, DATE FROM LOG WHERE RTF == 1 ORDER BY DATE;');
+    $db->do("CREATE TABLE life_log_temp_table AS SELECT * FROM LOG;");
+    $dbs = Settings::selectRecords($db, 'SELECT rowid, DATE FROM LOG WHERE ID_RTF >0 ORDER BY DATE;');
     #update  notes with new log id
     while(my @row = $dbs->fetchrow_array()) {
         my $sql_date = $row[1];
-        #$sql_date =~ s/T/ /;
-        $sql_date = DateTime::Format::SQLite->parse_datetime($sql_date);
-        $sql = "SELECT rowid, DATE FROM life_log_temp_table WHERE RTF = 1 AND DATE = '".$sql_date."';";
-        $dbs = dbExecute($sql);
-        my @new  = $dbs->fetchrow_array();
-        if(scalar @new > 0){
-            $db->do("UPDATE NOTES SET LID =". $new[0]." WHERE LID==".$row[0].";");
+        if($sql_date){#could be an improperly deleted record in there? Skip if there is!
+                        #$sql_date =~ s/T/ /;
+                        $sql_date = DateTime::Format::SQLite->parse_datetime($sql_date);
+                        $sql = "SELECT rowid, DATE FROM life_log_temp_table WHERE ID_RTF > 0 AND DATE = '".$sql_date."';";
+                        $dbs = Settings::selectRecords($db, $sql);
+                        my @new  = $dbs->fetchrow_array();
+                        if(scalar @new > 0){
+                            $db->do("UPDATE NOTES SET LID =". $new[0]." WHERE LID==".$row[0].";");
+                        }
         }
     }
 
     # Delete Orphaned Notes entries.
-    $dbs = dbExecute("SELECT LID, LOG.rowid from NOTES LEFT JOIN LOG ON
+    $dbs = Settings::selectRecords($db, "SELECT LID, LOG.rowid from NOTES LEFT JOIN LOG ON
                                     NOTES.LID = LOG.rowid WHERE LOG.rowid is NULL;");
     while(my @row = $dbs->fetchrow_array()) {
         $db->do("DELETE FROM NOTES WHERE LID=$row[0];");
     }
-    $dbs = dbExecute('DROP TABLE LOG;');
-    $dbs = dbExecute(qq(CREATE TABLE LOG (
-                            ID_CAT TINY        NOT NULL,
-                            DATE   DATETIME    NOT NULL,
-                            LOG    VCHAR (128) NOT NULL,
-                            AMOUNT INTEGER,
-                            AFLAG TINY DEFAULT 0,
-                            RTF BOOL DEFAULT 0,
-                            STICKY BOOL DEFAULT 0
-                            );));
-    $dbs = dbExecute('INSERT INTO LOG (ID_CAT,DATE,LOG,AMOUNT,AFLAG, RTF)
-                                    SELECT ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF
-                                    FROM life_log_temp_table ORDER by DATE;');
-    $dbs = dbExecute('DROP TABLE life_log_temp_table;');
+    $db->do('DROP TABLE LOG;');
+    $db->do(&Settings::createLOGStmt);
+    $db->do(q(INSERT INTO LOG (ID_CAT, ID_RTF, DATE, LOG, AMOUNT,AFLAG)
+                    SELECT ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG FROM life_log_temp_table ORDER by DATE;));
+    $db->do('DROP TABLE life_log_temp_table;');
 }
 
 1;
\ No newline at end of file
diff --git a/htdocs/cgi-bin/configFileTester.pl b/htdocs/cgi-bin/configFileTester.pl
deleted file mode 100755 (executable)
index 11628d6..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Programed by: Will Budic
-# Open Source License -> https://choosealicense.com/licenses/isc/
-#
-use strict;
-use warnings;
-use Try::Tiny;
-
-use DateTime;
-use DateTime::Format::SQLite;
-use DateTime::Duration;
-use Text::CSV;
-
-#DEFAULT SETTINGS HERE!
-use lib "system/modules";
-
-use lib $ENV{'PWD'}.'/htdocs/cgi-bin/system/modules';
-require CNFParser;
-
-my $cnf = CNFParser->new();
-
-$cnf->parse($ENV{'PWD'}."/dbLifeLog/database.cnf");
-
-foreach ($cnf->SQLStatments()){
-    print "$_\n";
-}
-foreach my $p ($cnf->constants()){
-
-    print "$p=", $cnf->constant($p),"\n";
-}
-# foreach (sort keys %ENV) {
-#   print "$_= $ENV{$_}\n";
-# }
-
-### CGI END
-1;
similarity index 62%
rename from htdocs/cgi-bin/remove.cgi
rename to htdocs/cgi-bin/data.cgi
index db7792bf5eb490cad98879f5c8f44471eb25f206..6fa00f4936b34d3b1f5eb58960227011e48a8b3c 100755 (executable)
@@ -6,13 +6,14 @@
 
 use strict;
 use warnings;
-use Try::Tiny;
 use Switch;
 
+
 use CGI;
 use CGI::Session '-ip_match';
 use DBI;
+use Exception::Class ('LifeLogException');
+use Syntax::Keyword::Try;
 
 use DateTime qw();
 use DateTime::Format::SQLite;
@@ -37,14 +38,14 @@ if(!$userid||!$dbname){
 
 my $database = Settings::logPath().$dbname;
 my $dsn= "DBI:SQLite:dbname=$database";
-my $db = DBI->connect($dsn, $userid, $password, { RaiseError => 1 }) or die "<p>Error->"& $DBI::errstri &"</p>";
+my $db = DBI->connect($dsn, $userid, $password, { RaiseError => 1 })  or LifeLogException->throw($DBI::errstri);
 
 #Fetch settings
 my $imgw = 210;
 my $imgh = 120;
 Settings::getConfiguration($db);
 Settings::getTheme();
-
+my $human = DateTime::Format::Human::Duration->new();
 
 
 ### Page specific settings Here
@@ -52,52 +53,35 @@ my $PRC_WIDTH = &Settings::pagePrcWidth;
 my $TH_CSS = &Settings::css;
 my $BGCOL  = &Settings::bgcol;
 #Set to 1 to get debug help. Switch off with 0.
-my $DEBUG        = &Settings::debug;
+my $DEBUG  = &Settings::debug;
 #END OF SETTINGS
 
 my $today = DateTime->now;
 $today->set_time_zone(&Settings::timezone);
 
-my %hshCats ={};
 my $tbl_rc =0;
-my $stm;
-my $stmtCat = "SELECT ID, NAME FROM CAT;";
-my $st = $db->prepare( $stmtCat );
-my $rv = $st->execute();
+my ($stm,$st, $rv);
 
-
-while(my @row = $st->fetchrow_array()) {
-    $hshCats{$row[0]} = $row[1];
-}
-
-
-my $tbl = '<form name="frm_log_del" action="remove.cgi" onSubmit="return formDelValidation();">
+my $tbl = '<a name="top"></a><form name="frm_log_del" action="data.cgi" onSubmit="return formDelValidation();">
            <table class="tbl_rem" width="'.$PRC_WIDTH.'%">
-           <tr class="hdr" style="text-align:left;"><th>Date</th> <th>Time</th><th>Log</th><th>Category</th></tr>';
+           <tr class="hdr" style="text-align:left;"><th>Date <a href="#bottom">&#x21A1;</a></th> <th>Time</th> <th>Log</th> <th>Category</th></tr>';
 
 
 my $datediff = $cgi->param("datediff");
 my $confirmed = $cgi->param('confirmed');
 if ($datediff){
-         print $cgi->header(-expires=>"+6os");    
+         print $cgi->header(-expires=>"+6os");
          print $cgi->start_html(-title => "Date Difference Report", -BGCOLOR => $BGCOL,
                  -script=>{-type => 'text/javascript', -src => 'wsrc/main.js'},
                  -style =>{-type => 'text/css', -src => "wsrc/$TH_CSS"}
 
-        );       
+        );
         &DisplayDateDiffs;
-}elsif (!$confirmed){
-         print $cgi->header(-expires=>"+6os");    
-         print $cgi->start_html(-title => "Personal Log Record Removal", -BGCOLOR => $BGCOL,
-                 -script=>{-type => 'text/javascript', -src => 'wsrc/main.js'},
-                 -style =>{-type => 'text/css', -src => "wsrc/$TH_CSS"}
-
-        );       
-
-
-        &NotConfirmed;
+}elsif ($confirmed){
+    &ConfirmedDelition;
 }else{
-        &ConfirmedDelition;
+    print $cgi->redirect('main.cgi') if not $cgi->param('chk');
+    &NotConfirmed;
 }
 
 
@@ -106,104 +90,136 @@ $db->disconnect();
 exit;
 
 sub DisplayDateDiffs{
+
     $tbl = '<table class="tbl" width="'.$PRC_WIDTH.'%">
         <tr class="r0"><td colspan="2"><b>* DATE DIFFERENCES *</b></td></tr>';
 
-    $stm = 'SELECT DATE, LOG FROM LOG WHERE '; 
+    $stm = 'SELECT DATE, LOG FROM VW_LOG WHERE ';
     my  @ids = $cgi->param('chk');
 
+     @ids = reverse @ids;
+
     foreach (@ids){
-        $stm .= "rowid = '" . $_ ."'";
+        $stm .= "PID = " . $_ ."";
         if(  \$_ != \$ids[-1]  ) {
             $stm = $stm." OR ";
         }
     }
-    $stm .= ';';
+    $stm .= ' ORDER BY PID;';
+    print $cgi->pre("###[stm:$stm]") if($DEBUG);
     $st = $db->prepare( $stm );
-    $st->execute() or die or die "<p>Error->"& $DBI::errstri &"</p>";
+    $st->execute();
 
-    my $dt_prev = $today;
+    my ($dt,$dif,$first,$last,$tnext, $dt_prev) = (0,0,0,0,0,$today);
     while(my @row = $st->fetchrow_array()) {
-
-         my $dt = DateTime::Format::SQLite->parse_datetime( $row[0] );
-         my $dif = dateDiff($dt_prev, $dt);
-         $tbl .= '<tr class="r1"><td>'. $dt->ymd . '</td> 
-                    </td><td style="text-align:left;">'.$row[1]."</td></tr>".
-                 '<tr class="r0"><td colspan="2">'.$dif. '</td> </tr>';        
-        $dt_prev = $dt;
+         my $rdat = $row[0];
+         my $rlog = $row[1];
+         $rlog =~ m/\n/;
+         $dt  = DateTime::Format::SQLite->parse_julianday( $rdat );
+         $dt->set_time_zone(&Settings::timezone);
+         $dif = dateDiff($dt_prev, $dt);
+         $tbl .= '<tr class="r1"><td>'. $dt->ymd . '</td>
+                    </td><td style="text-align:left;">'.$rlog."</td></tr>".
+                 '<tr class="r0"><td colspan="2">'.$dif.'</td> </tr>';
+         $last = $dt_prev;
+         $dt_prev = $dt;
+         if($tnext){
+             $dif = dateDiff($today, $dt,'');
+             $tbl .= '<tr class="r0"><td colspan="2">'.$dif. '</td> </tr>';
+         }
+         else{$tnext=1; $first = $dt;}
+    }
+    if($first != $last){
+        $dif = dateDiff($first, $dt_prev,'(first above)');
+        $tbl .= '<tr class="r0"><td colspan="2">'.$dif. '</td> </tr>';
     }
     $tbl .= '</table>';
 
-print '<center><div>'.$tbl.'</div><br><div><a href="main.cgi">Back to Main Log</a></div></center>';
+print '<a name="top"></a><center><div>'.$tbl.'</div><br><div><a href="main.cgi">Back to Main Log</a></div></center>';
 }
 
 
-sub dateDiff{
-    my($d1,$d2)=@_;
-    my $span = DateTime::Format::Human::Duration->new();
-    my $dur = $span->format_duration($d2 - $d1);
-return sprintf( "%s <br>between %s and %s", $dur, boldDate($d1), boldDate($d2));
+sub dateDiff {
+    my($d1,$d2,$ff,$sw)=@_;
+    if($d1->epoch()>$d2->epoch()){
+        $sw = $d1;
+        $d1 = $d2;
+        $d2 = $sw;
+    }else{$sw="";}
+    my $dur = $human->format_duration_between($d1, $d2);
+    my ($t1,$t2) = ("","");
+    $t1 = "<font color='red'> today </font>" if ($d1->ymd() eq $today->ymd());# Notice in perl == can't be used here!
+    $t2 = "<font color='red'> today </font>" if ($d2->ymd() eq $today->ymd());
+return sprintf( "%s <br>between $ff $t1 %s and $t2 %s[%s]", $dur, boldDate($d1), boldDate($d2), $d1->ymd());
 
 }
 
-sub boldDate{
+sub boldDate {
     my($d)=@_;
-return "<b>".$d->ymd."</b> ".$d->hms;
+return "<b>".$d->ymd()."</b> ".$d->hms;
 }
 
 
-sub ConfirmedDelition{
+sub ConfirmedDelition {
 
+try{
 
     foreach my $id ($cgi->param('chk')){
-        
+        print $cgi->p("###[deleting:$id]")  if(Settings::debug());
         $st = $db->prepare("DELETE FROM LOG WHERE rowid = '$id';");
         $rv = $st->execute() or die or die "<p>Error->"& $DBI::errstri &"</p>";
         $st = $st = $db->prepare("DELETE FROM NOTES WHERE LID = '$id';");
         $rv = $st->execute();
 
-        if($rv < 0) {
-             print "<p>Error->"& $DBI::errstri &"</p>";
-             exit;
-        }
-        
+       # if($rv == 0) {
+          #   die "<p>Error->"& $DBI::errstri &"</p>";
+       # }
+
     }
-    
-    
     $st->finish;
 
     print $cgi->redirect('main.cgi');
 
+}catch{
+    print $cgi->p("<font color=red><b>ERROR</b></font>  " . $_);
 }
 
-sub NotConfirmed{
+}
 
-    my $stmS = "SELECT rowid, ID_CAT, DATE, LOG from LOG WHERE";
-    my $stmE = " ORDER BY DATE DESC, rowid DESC;";
+sub NotConfirmed {
+
+    my $stmS = "SELECT ID, PID, (select NAME from CAT WHERE ID_CAT == CAT.ID) as CAT, DATE, LOG from VW_LOG WHERE";
+    my $stmE = " ORDER BY DATE DESC, ID DESC;";
 
     #Get ids and build confirm table and check
     my $stm = $stmS ." ";
         foreach my $id ($cgi->param('chk')){
-            $stm = $stm . "rowid = '" . $id . "' OR ";
+            $stm = $stm . "PID = " . $id . " OR ";
         }
-    #OR end to rid=0 hack! ;)
-        $stm = $stm . "rowid = '0' " . $stmE;
-    #
+        $stm =~ s/ OR $//; $stm .= $stmE;
+
     $st = $db->prepare( $stm );
-    $rv = $st->execute() or die "<p>Error->"& $DBI::errstri &"</p>";
-    if($rv < 0) {
-            print "<p>Error->"& $DBI::errstri &"</p>";
-    }
+    $rv = $st->execute();
+    print $cgi->header(-expires=>"+6os");
+    print $cgi->start_html(-title => "Personal Log Record Removal", -BGCOLOR => $BGCOL,
+            -script=>{-type => 'text/javascript', -src => 'wsrc/main.js'},
+            -style =>{-type => 'text/css', -src => "wsrc/$TH_CSS"}
+
+    );
+
+    print $cgi->pre("###NotConfirmed($rv,$st)->[stm:$stm]")  if($DEBUG);
 
     my $r_cnt = 0;
     my $rs = "r1";
+
+
     while(my @row = $st->fetchrow_array()) {
 
-        my $ct = $hshCats{$row[1]};
-        my $dt = DateTime::Format::SQLite->parse_datetime( $row[2] );
-        my $log = log2html($row[3]);
-        
-        $tbl = $tbl . '<tr class="r1"><td class="'.$rs.'">'. $dt->ymd . "</td>" . 
+        my $ct = $row[2];
+        my $dt = DateTime::Format::SQLite->parse_datetime( $row[3] );
+        my $log = log2html($row[4]);
+
+        $tbl = $tbl . '<tr class="r1"><td class="'.$rs.'">'. $dt->ymd . "</td>" .
             '<td class="'.$rs.'">' . $dt->hms . "</td>" .
             '<td class="'.$rs.'" style="font-weight:bold; color:maroon;">'."$log</td>\n".
             '<td class="'.$rs.'">' . $ct. '<input type="hidden" name="chk" value="'.$row[0].'"></td></tr>';
@@ -220,9 +236,9 @@ sub NotConfirmed{
         $plural = "s";
     }
 
- $tbl = $tbl .  '<tr class="r0"><td colspan="4">
+ $tbl = $tbl .  '<tr class="r0"><td colspan="4"><a name="bottom"></a><a href="#top">&#x219F;</a>
  <center>
- <h2>Please Confirm You Want <br/>The Above Record'.$plural.' Deleted?</h2>
+ <h2>Please Confirm You Want<br>The Above Record'.$plural.' Deleted?</h2>
  (Or hit you Browsers Back Button!)</center>
  </td></tr>
  <tr class="r0"><td colspan="4"><center>
@@ -240,7 +256,7 @@ print '<center><div>' . $tbl .'</div></center>';
 sub log2html{
     my $log = shift;
     my ($re_a_tag, $sub)  = qr/<a\s+.*?>.*<\/a>/si;
-    $log =~ s/''/'/g;    
+    $log =~ s/''/'/g;
     $log =~ s/\r\n/<br>/gs;
     $log =~ s/\\n/<br>/gs;
 
@@ -249,7 +265,7 @@ sub log2html{
         my $len = index( $log, '>', $idx );
         $sub = substr( $log, $idx + 1, $len - $idx - 1 );
         my $url = qq(<a href="$sub" target=_blank>$sub</a>);
-        $log =~ s/<<LNK<(.*?)>/$url/osi;
+        $log =~ s/<<LNK<(.*?)>+/$url/osi;
     }
 
     if ( $log =~ /<<IMG</ ) {
@@ -257,7 +273,7 @@ sub log2html{
             my $len = index( $log, '>', $idx );
             $sub = substr( $log, $idx + 1, $len - $idx - 1 );
             my $url = qq(<img src="$sub"/>);
-            $log =~ s/<<IMG<(.*?)>/$url/osi;
+            $log =~ s/<<IMG<(.*?)>+/$url/osi;
     }
     elsif ( $log =~ /<<FRM</ ) {
             my $idx = $-[0] + 5;
@@ -281,7 +297,7 @@ sub log2html{
                 #TODO fetch from web locally the original image.
                 $lnk =  qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
             }
-            $log =~ s/<<FRM<(.*?)>/$lnk/o;
+            $log =~ s/<<FRM<(.*?)>+/$lnk/o;
         }
 
     #Replace with a full link an HTTP URI
@@ -300,7 +316,7 @@ sub log2html{
         $log =~ s/$a/$b/o;
         $a = q(</iframe>);
         $b = q(</iframe></div>);
-        $log =~ s/$a/$b/o;        
+        $log =~ s/$a/$b/o;
     }
     else {
         my @chnks = split( /($re_a_tag)/si, $log );
index 9675152509216bec9f6592fa63f53c6baed0ba86..89f00ffe1bed9067d95910297df48c0a7b078759 100755 (executable)
@@ -26,25 +26,13 @@ use IO::Compress::Gzip qw(gzip $GzipError);
 use Compress::Zlib;
 
 
-#DEFAULT SETTINGS HERE!
-our $REC_LIMIT    = 25;
-our $TIME_ZONE    = 'Australia/Sydney';
-our $LANGUAGE     = 'English';
-our $PRC_WIDTH    = '60';
-our $LOG_PATH     = '../../dbLifeLog/';
-our $SESSN_EXPR   = '+30m';
-our $DATE_UNI     = '0';
-our $RELEASE_VER  = '1.5';
-our $AUTHORITY    = '';
-our $IMG_W_H      = '210x120';
-our $AUTO_WRD_LMT = 200;
-
-#END OF SETTINGS
+use lib "system/modules";
+require Settings;
+
 
 my $cgi = CGI->new;
-my $session =
-  new CGI::Session( "driver:File", $cgi, { Directory => $LOG_PATH } );
-my $sid      = $session->id();
+my $session = new CGI::Session("driver:File",$cgi, {Directory => Settings::logPath()});
+my $sid=$session->id();
 my $dbname   = $session->param('database');
 my $userid   = $session->param('alias');
 my $password = $session->param('passw');
@@ -53,17 +41,14 @@ my $lid      = $cgi->param('id');
 my $doc      = $cgi->param('doc');
 my $bg       = $cgi->param('bg');
 my $error    = "";
-my ($response, $json) = 'Session Expired';
+my ($nid,$response, $json) = 'Session Expired';
 
-my $lang  = Date::Language->new($LANGUAGE);
+#my $lang  = Date::Language->new($LANGUAGE);
 my $today = DateTime->now;
-$today->set_time_zone($TIME_ZONE);
+$today->set_time_zone(&Settings::timezone);
 
-if ($AUTHORITY) {
-    $userid = $password = $AUTHORITY;
-    $dbname = 'data_' . $userid . '_log.db';
-}
-elsif ( !$userid || !$dbname ) {   
+
+if  ( !$userid || !$dbname ) {
 
     &defaultJSON;
     print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
@@ -72,18 +57,17 @@ elsif ( !$userid || !$dbname ) {
    exit;
 }
 
-my $database = '../../dbLifeLog/' . $dbname;
-my $dsn      = "DBI:SQLite:dbname=$database";
-my $db = DBI->connect( $dsn, $userid, $password, { RaiseError => 1 } );
+my $database = Settings::logPath().$dbname;
+my $dsn= "DBI:SQLite:dbname=$database";
+my $db = DBI->connect($dsn, $userid, $password, { RaiseError => 1 });
 
-### Authenticate session to alias password
-#&authenticate;
+Settings::getConfiguration($db);
 
 
 my $strp = DateTime::Format::Strptime->new(
     pattern   => '%F %T',
     locale    => 'en_AU',
-    time_zone => $TIME_ZONE,
+    time_zone => Settings::timezone(),
     on_error  => 'croak',
 );
 
@@ -102,56 +86,55 @@ undef($session);
 exit;
 
 
-sub defaultJSON(){
+sub defaultJSON {
 
-     my $content = ""; 
+     my $content = "";
      if($action eq 'load' && !$error){
-         $content = JSON->new->utf8->decode($doc);         
+         $content = JSON->new->utf8->decode($doc);
      }
      $json = JSON->new->utf8->space_after->pretty->allow_blessed->encode
-     ({date => $strp->format_datetime($today), 
-       response_origin => "LifeLog.".$RELEASE_VER,       
+     ({date => $strp->format_datetime($today),
+       response_origin =>  "LifeLog.".Settings::release(),
        alias => $userid, log_id => $lid, database=>$database, action => $action, error=>$error,
-       response=>$response,       
+       response=>$response,
        content=>$content
-       #received => $doc     
-   });   
+       #received => $doc
+   });
 }
 
 sub processSubmit {
 
      # my $date = $cgi->param('date');
      my $st;
-  
-      try {
+
+    try {
         if($action eq 'store'){
 
-$doc = qq({
-"lid":"$lid",
-"bg":"$bg",           
-"doc":$doc
-});
+           $doc = qq({
+                        "lid":"$lid",
+                        "bg":"$bg",
+                        "doc":$doc
+                 });
            my $zip = compress($doc, Z_BEST_COMPRESSION);
-           $st = $db->prepare("SELECT LID FROM NOTES WHERE LID = '$lid';"); 
-           $st -> execute();
+              $st = $db->prepare("SELECT LID FROM NOTES WHERE LID = $lid;");
+              $st -> execute();
            if($st->fetchrow_array() eq undef) {
-               $st = $db->prepare("INSERT INTO NOTES(LID, DOC) VALUES (?, ?);");               
+               $st = $db->prepare("INSERT INTO NOTES(LID, DOC) VALUES (?, ?);");
                $st->execute($lid, $zip);
                $response = "Stored Document (id:$lid)!";
            }
            else{
-               $st = $db->prepare("UPDATE NOTES SET DOC = ? WHERE LID = '$lid';");
+               $st = $db->prepare("UPDATE NOTES SET DOC = ? WHERE LID = $lid;");
                $st->execute($zip);
                $response = "Updated Document (id:$lid)!";
            }
-
         }
         elsif($action eq 'load'){
-           $st = $db->prepare("SELECT DOC FROM NOTES WHERE LID = '$lid';"); 
+           $st = $db->prepare("SELECT DOC FROM NOTES WHERE LID = $lid;");
            $st -> execute();
            my @arr = $st->fetchrow_array();
-           if(!@arr){
-               $st = $db->prepare("SELECT DOC FROM NOTES WHERE LID = '0';"); 
+           if(@arr eq undef){
+               $st = $db->prepare("SELECT DOC FROM NOTES WHERE LID = '0';");
                $st -> execute();
                @arr = $st->fetchrow_array();
            }
@@ -167,23 +150,17 @@ $doc = qq({
             $error = "Your action ($action) sux's a lot!";
         }
 
-      }
-      catch {
-          $error = ":LID[$lid]-> ".$_;
+    }catch {
+        $error = ":LID[$lid]-> ".$_;
     }
 }
 
 
 sub authenticate {
-      try {
+    try {
 
-          if ($AUTHORITY) {
-              return;
-          }
 
-          my $st = $db->prepare(
-              "SELECT * FROM AUTH WHERE alias='$userid' and passw='$password';"
-          );
+          my $st = $db->prepare("SELECT * FROM AUTH WHERE alias='$userid' and passw='$password';");
           $st->execute();
           if ( $st->fetchrow_array() ) { return; }
 
@@ -200,7 +177,7 @@ sub authenticate {
               return;
           }
 
-          
+
 
           print $cgi->center( $cgi->div("<b>Access Denied!</b> alias:$userid pass:$password") );
 
@@ -209,15 +186,11 @@ sub authenticate {
           $session->flush();
           exit;
 
-      }
-      catch {
-          print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
-          print $cgi->p( "ERROR:" . $_ );
-          print $cgi->end_html;
-          exit;
+    }catch {
+        print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
+        print $cgi->p( "ERROR:" . $_ );
+        print $cgi->end_html;
+        exit;
     }
 }
-
-
-
-
+1;
\ No newline at end of file
index 8e5bc799dfc14cafd52e3980368f934cfb0ff257..ef02d337f633d60a7ac7aa6273bce69e943ec98b 100755 (executable)
@@ -5,7 +5,8 @@
 #
 use strict;
 use warnings;
-use Try::Tiny;
+use Exception::Class ('LifeLogException');
+use Syntax::Keyword::Try;
 use CGI;
 use CGI::Session '-ip_match';
 use DBI;
@@ -13,13 +14,13 @@ use DBI;
 use DateTime;
 use DateTime::Format::SQLite;
 use DateTime::Duration;
-use Text::CSV;
-
+#Bellow perl 5.28+
+#use experimental 'smartmatch';
 
 #DEFAULT SETTINGS HERE!
 use lib "system/modules";
 require Settings;
-
+my $BACKUP_ENABLED = 0;
 
 my $cgi = CGI->new;
 my $session = new CGI::Session("driver:File",$cgi, {Directory=>&Settings::logPath});
@@ -35,161 +36,249 @@ my ($debug,$frm) = "";
 #Codebase release version. Release in the created db or existing one can be different, through time.
 my $RELEASE = Settings::release();
 
-#This is the OS developer release key, replace on istallation. As it is not secure.
-my $cipher_key = '95d7a85ba891da';
-
 if($cgi->param('logout')){&logout}
 
-&checkAutologinSet;
-if(&processSubmit==0){
-
-    print $cgi->header(-expires=>"0s", -charset=>"UTF-8", -cookie=>$cookie);
-    print $cgi->start_html(
-    -title   => "Personal Log Login",
-    -BGCOLOR => &Settings::bgcol,
-    -script => [
-                { -type => 'text/javascript', -src => 'wsrc/main.js' },    ],
-    -style  => [
-                { -type => 'text/css', -src => 'wsrc/'.&Settings::css }
-            ]
-);
-
-my @ht = split(m/\s/,`hostname -I`);
-my $hst = `hostname` . "($ht[0])";
-
-$frm = qq(
-     <form id="frm_login" action="login_ctr.cgi" method="post"><table border="0" width=").&Settings::pagePrcWidth.qq(%">
-      <tr class="r0">
-         <td colspan="3"><center>LOGIN</center></td>
-        </tr>
-      <tr class="r1" style="border-left:1px solid black; border-right:1px solid black;">
-         <td align="right">Alias:</td><td><input type="text" name="alias" value="$alias"/></td><td></td>
-         </tr>
-      <tr class="r1" style="border-left:1px solid black; border-right:1px solid black;">
-         <td align="right">Password:</td><td><input type="password" name="passw" value="$passw"/></td><td></td>
-        </tr>
-        <tr class="r1">
-         <td colspan="3" style="border-left:1px solid black; border-right:1px solid black;"><font color="red">NOTICE!</font> &nbsp;
-         Alias will create a new database if it doesn't exist. Note down your password.
-         <input type="hidden" name="CGISESSID" value="$sid"/>
-         <input type="hidden" name="login" value="1"/></td></tr>
-      <tr class="r0"><td colspan="2">Your Host -> <b>$hst</b></td><td><input type="submit" value="Login"/></td></tr>
-    </table></form>);
-
-print qq(<br><br><div id="rz">
-                        <center>
-                            <h2>Welcome to Life Log</h2><div>$frm</div><br>
-                            <a href="https://github.com/wbudic/LifeLog" target="_blank">Get latest version of this application here!</a><br>
-                        </center><div>);
-
-Settings::printDebugHTML($debug) if (&Settings::debug);
-print $cgi->end_html;
-
+try{
+    &checkAutologinSet;
+    if(&processSubmit==0){
+
+        print $cgi->header(-expires=>"0s", -charset=>"UTF-8", -cookie=>$cookie);
+        print $cgi->start_html(
+        -title   => "Personal Log Login",
+        -BGCOLOR => &Settings::bgcol,
+        -script => [
+                    { -type => 'text/javascript', -src => 'wsrc/main.js' },    ],
+        -style  => [
+                    { -type => 'text/css', -src => 'wsrc/'.&Settings::css }
+                ]
+        );
 
-}
-else{
-    print $cgi->start_html;
+    my @ht = split(m/\s/,`hostname -I`);
+    my $hst = "";
+       $hst = `hostname` . "($ht[0])" if (@ht);
+
+    $frm = qq(
+        <form id="frm_login" action="login_ctr.cgi" method="post"><table border="0" width=").&Settings::pagePrcWidth.qq(%">
+        <tr class="r0">
+            <td colspan="3"><center>LOGIN</center></td>
+            </tr>
+        <tr class="r1" style="border-left:1px solid black; border-right:1px solid black;">
+            <td align="right">Alias:</td><td><input type="text" name="alias" value="$alias"/></td><td></td>
+            </tr>
+        <tr class="r1" style="border-left:1px solid black; border-right:1px solid black;">
+            <td align="right">Password:</td><td><input type="password" name="passw" value="$passw"/></td><td></td>
+            </tr>
+            <tr class="r1">
+            <td colspan="3" style="border-left:1px solid black; border-right:1px solid black;"><font color="red">NOTICE!</font> &nbsp;
+            Alias will create a new database if it doesn't exist. Note down your password.
+            <input type="hidden" name="CGISESSID" value="$sid"/>
+            <input type="hidden" name="login" value="1"/></td></tr>
+        <tr class="r0"><td colspan="2">Your Host -> <b>$hst</b></td><td><input type="submit" value="Login"/></td></tr>
+        </table></form>);
+
+    print qq(<br><br><div id="rz">
+                            <center>
+                                <h2>Welcome to Life Log</h2><div>$frm</div><br>
+                                <a href="https://github.com/wbudic/LifeLog" target="_blank">Get latest version of this application here!</a><br>
+                            </center><div>);
+
+    Settings::printDebugHTML($debug) if (&Settings::debug);
     print $cgi->end_html;
-}
 
+    }
+    else{
+        print $cgi->start_html;
+        print $cgi->end_html;
+    }
+}
+ catch {
+            my $err = $@;
+            my $dbg = "" ;
+            my $pwd = `pwd`;
+            $pwd =~ s/\s*$//;
+            $dbg = "--DEBUG OUTPUT--\n$debug" if $debug;
+            print $cgi->header,
+            "<hr><font color=red><b>SERVER ERROR</b></font> on ".DateTime->now.
+            "<pre>".$pwd."/$0 -> &".caller." -> [$err]","\n$dbg</pre>",
+            $cgi->end_html;
+ };
 exit;
 
-sub processSubmit{
-try{
-
+sub processSubmit {
     if($alias&&$passw){
 
-            $passw = uc crypt $passw, hex $cipher_key;
+            $passw = uc crypt $passw, hex Settings->CIPHER_KEY;
             #CheckTables will return 1 if it was an logout set in config table.
             if(&checkCreateTables()==0){
                 $session->param('alias', $alias);
                 $session->param('passw', $passw);
                 $session->param('database', 'data_'.$alias.'_log.db');
                 $session->flush();
+                ### To MAIN PAGE
                 print $cgi->header(-expires=>"0s", -charset=>"UTF-8", -cookie=>$cookie, -location=>"main.cgi");
-                return 1; #activate redirect to main, main will check credentials.
+                ###
+                return 1; #activated redirect to main, main will check credentials.
             }
     }
     else{
         $alias = $passw = "";
     }
     &Settings::removeOldSessions;  #and prompt for login returning 0
-return 0;
-}
- catch{
-        print $cgi->header;
-        print "<font color=red><b>SERVER ERROR processSubmit()</b></font>: $_ dump ->". $session->dump();
-        print $cgi->end_html;
- }
+    return 0;
 }
 
 sub checkAutologinSet {
-try{
-        #We don't need to slurp as it is expected setting in header.
-        my @cre;
-        open(my $fh, '<', &Settings::logPath.'main.cnf' ) or die "Can't open main.cnf: $!";
-        while (my $line = <$fh>) {
-                    chomp $line;
-                    if(rindex ($line, "<<AUTO_LOGIN<", 0)==0){
-                         my $end = index $line, ">", 14;
-                         my $crest = substr $line, 13, $end - 13;
-                         @cre = split '/', $crest;
-                         last;
-                    }
-        }
+
+    #We don't need to slurp as it is expected setting in header.
+    my (@cre, $end,$crest);
+    open(my $fh, '<', &Settings::logPath.'main.cnf' ) or LifeLogException->throw("Can't open main.cnf: $!");
+    while (my $line = <$fh>) {
+                chomp $line;
+                if(rindex ($line, "<<AUTO_LOGIN<", 0)==0){
+                        $end = index $line, ">", 14;
+                        $crest = substr $line, 13, $end - 13;
+                        @cre = split '/', $crest;
+                       next;
+                }
+                elsif(rindex ($line, "<<BACKUP_ENABLED<", 0)==0){
+                        $end = index $line, ">", 18;
+                        $BACKUP_ENABLED = substr $line, 17, $end - 17;
+                    last; #we expect as last anon to be set.
+                }
+                elsif(rindex ($line, "<<CONFIG<",0) == 0){last;}
+    }
     close $fh;
-        if(@cre &&scalar(@cre)>1){
-             my $database = &Settings::logPath.'data_'.$cre[0].'_log.db';
-             my $dsn= "DBI:SQLite:dbname=$database";
-             my $db = DBI->connect($dsn, $cre[0], $cre[1], { RaiseError => 1 })
-                                or die "<p>Error->"& $DBI::errstri &"</p>";
-                    #check if enabled.
-             my $st = $db->prepare("SELECT VALUE FROM CONFIG WHERE NAME='AUTO_LOGIN';");
-                     $st->execute();
-             my @set = $st->fetchrow_array();
-                    if(@set && $set[0]=="1"){
-                         $alias = $cre[0];
-                         $passw = $cre[1];
-                         &Settings::removeOldSessions;
-                    }
-             $db->disconnect();
-        }
-}
- catch{
-      print $cgi->header;
-      print "<font color=red><b>SERVER ERROR</b></font>:".$_;
-      print $cgi->end_html;
-      exit;
- }
+    if(@cre &&scalar(@cre)>1){##TODO we already connected here to the db, why do it again later?
+            my $database = &Settings::logPath.'data_'.$cre[0].'_log.db';
+            my $dsn= "DBI:SQLite:dbname=$database";
+            my $db = DBI->connect($dsn, $cre[0], $cre[1], { RaiseError => 1 })
+                       or LifeLogException->throw("<p>Error->"& $DBI::errstri &"</p>");
+                #check if enabled.
+            my $st = $db->prepare("SELECT VALUE FROM CONFIG WHERE NAME='AUTO_LOGIN';");
+            $st->execute();
+            my @set = $st->fetchrow_array();
+            if(@set && $set[0]=="1"){
+                    $alias = $cre[0];
+                    $passw = $cre[1];
+                    &Settings::removeOldSessions;
+            }
+            $db->disconnect();
+    }
+
 }
 
 sub checkCreateTables {
-try{
+
     my $today = DateTime->now;
-       $today->set_time_zone( &Settings::timezone );
+       $today-> set_time_zone( &Settings::timezone );
     my $database = &Settings::logPath.'data_'.$alias.'_log.db';
     my $dsn= "DBI:SQLite:dbname=$database";
-    my $db = DBI->connect($dsn, $alias, $passw, { RaiseError => 1 })
-              or die "<p>Error->"& $DBI::errstri &"</p>";
-    my $rv;
-    my $st = $db->prepare(selSQLTbl('LOG'));
-       $st->execute();
-
-    my $changed = 0;
-
-    if(!$st->fetchrow_array()) {
-        my $stmt = qq(
-        CREATE TABLE LOG (
-             ID_CAT TINY NOT NULL,
-             DATE DATETIME  NOT NULL,
-             LOG VCHAR(128) NOT NULL,
-             AMOUNT INTEGER DEFAULT 0,
-             AFLAG TINY DEFAULT 0,
-             RTF BOOL DEFAULT 0,
-             STICKY BOOL DEFAULT 0
-        );
-        CREATE INDEX idx_log_dates ON LOG (DATE);
-        );
+    my $db = DBI->connect($dsn, $alias, $passw, { RaiseError => 1 }) or LifeLogException->throw($DBI::errstri);
+    my ($pst, $sql,$rv, $changed) = 0;
+    # We live check database for available tables now only once.
+    # If brand new database, this sill returns fine an empty array.
+    my $pst = Settings::selectRecords($db,"SELECT name FROM sqlite_master WHERE type='table' or type='view';");
+    my %curr_tables = ();
+    while(my @r = $pst->fetchrow_array()){
+        $curr_tables{$r[0]} = 1;
+    }
+    if($curr_tables{'CONFIG'}) {
+        #Set changed if has configuration data been wiped out.
+        $changed = 1 if Settings::countRecordsIn($db, 'CONFIG') == 0;
+    }
+    else{
+        #v.1.3 -> v.1.4
+        #has alter table CONFIG add DESCRIPTION VCHAR(128);
+        $rv = $db->do(&Settings::createCONFIGStmt);
+        $changed = 1;
+    }
+    # Now we got a db with CONFIG, lets get settings from there.
+    # Default version is the scripted current one, which could have been updated.
+    # We need to maybe update further, if these versions differ.
+    # Source default and the one from the CONFIG table in the (present) database.
+    my $DEF_VERSION = Settings::release();
+                      Settings::getConfiguration($db,{backup_enabled=>$BACKUP_ENABLED});
+    my $DB_VERSION  = Settings::release();
+    my $hasLogTbl   = $curr_tables{'LOG'};
+    my $hasNotesTbl = $curr_tables{'NOTES'};
+    my @annons = Settings::anons();
+    LifeLogException -> throw("Annons!") if (@annons==0);#We even added above the backup_enabled anon, so WTF?
+
+    # Reflect anons to db config.
+    $sql = "SELECT ID, NAME, VALUE FROM CONFIG WHERE";
+    foreach my $ana(@annons){$sql .=  " NAME LIKE '$ana' OR";};$sql =~ s/OR$//; $sql .=';';
+    $pst =  Settings::selectRecords($db, $sql);
+    while(my @row = $pst->fetchrow_array()) {
+        my ($vid,$n,$sv, $dv) = ($row[0], $row[1], Settings::anon($row[1]), $row[2]);
+        $db->do("UPDATE CONFIG SET VALUE='$sv' WHERE ID=$vid;") if($dv ne $sv);
+    }
+    #
+    # From v.1.8 Log has changed, to have LOG to NOTES relation.
+    #
+    if($hasLogTbl && $DEF_VERSION > $DB_VERSION && $DB_VERSION < 1.8){
+        # We must upgrade now. If existing LOG table is now invalid old version containing boolean RTF.
+        Settings::debug(1);
+        my @names = @{Settings::getTableColumnNames($db, 'LOG')};
+        #perl 5.28+ <--
+        #if ( 'RTF' ~~ @names ) {
+        if(grep( /RTF/, @names)){
+            $db->do('DROP TABLE life_log_login_ctr_temp_table;') if($curr_tables{'life_log_login_ctr_temp_table'});
+            $db->do('CREATE TABLE life_log_login_ctr_temp_table AS SELECT * FROM LOG;');
+            my %notes_ids = ();
+            if($hasNotesTbl){
+                $pst =  Settings::selectRecords($db, 'SELECT rowid, DATE FROM LOG WHERE RTF > 0 ORDER BY DATE;');
+                while(my @row = $pst->fetchrow_array()) {
+                        my $sql_date = $row[1];;
+                        $sql_date = DateTime::Format::SQLite->parse_datetime($sql_date);
+                        my $pst2  = Settings::selectRecords($db, "SELECT rowid, DATE FROM life_log_login_ctr_temp_table WHERE RTF > 0 AND DATE = '".$sql_date."';");
+                        my @rec   = $pst2->fetchrow_array();
+                        if(@rec){
+                            $db->do("UPDATE NOTES SET LID =". $rec[0]." WHERE LID ==".$row[0].";");
+                            $pst2  = Settings::selectRecords($db, "SELECT rowid FROM NOTES WHERE LID == ".$rec[0].";");
+                            @rec   = $pst2->fetchrow_array();
+                            if(@rec){
+                                    $notes_ids{$sql_date} = $rec[0];
+                            }
+                        }
+                }
+
+            }
+            $db->do('DROP TABLE LOG;');
+            #v.1.8 Has fixes, time also properly to take into the sort. Not crucial to drop.
+            $db->do('DROP TABLE VW_LOG;');delete($curr_tables{'VW_LOG'});
+            $db->do(&Settings::createLOGStmt);
+            $db->do('INSERT INTO LOG (ID_CAT, DATE, LOG, AMOUNT,AFLAG)
+                                SELECT ID_CAT, DATE, LOG, AMOUNT, AFLAG FROM life_log_login_ctr_temp_table ORDER by DATE;');
+            $db->do('DROP TABLE life_log_login_ctr_temp_table;');
+
+            #Experimental sofar NOTES table has LID changed to proper number type.
+            $db->do(qq(CREATE TABLE life_log_rename_column_new_table (
+                           LID INTEGER NOT NULL PRIMARY KEY,    DOC    TEXT);));
+            $db->do('INSERT INTO life_log_rename_column_new_table SELECT `LID`,`DOC` FROM `NOTES`;');
+            $db->do('DROP TABLE `NOTES`;');
+            $db->do('ALTER TABLE `life_log_rename_column_new_table` RENAME TO `NOTES`');
+
+
+            #Update new LOG with notes RTF ids, in future versions, this will never be required anymore.
+            foreach my $date (keys %notes_ids){
+                #next if(ref($notes_ids{$date}) eq 'HASH');
+                my $nid = $notes_ids{$date};
+                my $stmt= "UPDATE LOG SET ID_RTF =". $nid." WHERE DATE == '".$date."';";
+                try{
+                    $db->do($stmt);
+                }
+                 catch{
+                        LifeLogException -> throw(error=>"Upgrade statement -> [$stmt] failed!", show_trace=>1);
+                 }
+            }
+            undef %notes_ids;
+        }
+        #Version change still detected above.
+        #Need to run slow populuate check from config file.
+        $changed = 1;
+    }
+
+    if(!$hasLogTbl) {
 
         if($sssCreatedDB){
             print $cgi->header;
@@ -200,128 +289,61 @@ try{
             exit;
         }
 
-        $db->do($stmt);
+        $db->do(&Settings::createLOGStmt);
 
-        $st = $db->prepare('INSERT INTO LOG(ID_CAT,DATE,LOG) VALUES (?,?,?)');
-        $st->execute( 3, $today, "DB Created!");
-        $session->param("cdb", "1");
+        my $st = $db->prepare('INSERT INTO LOG(ID_CAT,DATE,LOG) VALUES (?,?,?)');
+            $st->execute( 3, $today, "DB Created!");
+            $session->param("cdb", "1");
     }
 
     # From v.1.6 view use server side views, for pages and correct record by ID and PID lookups.
-    # This should make queries faster, less convulsed, and log renumeration less needed, for accurate pagination.
-    $st = $db->prepare(selSQLView('VW_LOG'));
-    $st->execute();
-    if(!$st->fetchrow_array()) {
-        $rv = $db->do('CREATE VIEW VW_LOG AS
-                              SELECT rowid as ID,*, (select count(rowid) from LOG as recount where a.rowid >= recount.rowid) as PID
-                              FROM LOG as a ORDER BY DATE DESC;');
-        if($rv < 0){print "<p>Error->"& $DBI::errstri &"</p>";}
+    # This should make queries faster, less convulsed, and log renumeration less needed for accurate pagination.
+    if(!$curr_tables{'VW_LOG'}) {
+        $rv = $db->do(&Settings::createVW_LOGStmt);
     }
-
-    $st = $db->prepare(selSQLTbl('CAT'));
-    $st->execute();
-    if(!$st->fetchrow_array()) {
-         my $stmt = qq(
-                        CREATE TABLE CAT(
-                            ID TINY PRIMARY KEY NOT NULL,
-                            NAME VCHAR(16),
-                            DESCRIPTION VCHAR(64)
-                        );
-                        CREATE INDEX idx_cat_name ON CAT (NAME);
-         );
-        $rv = $db->do($stmt);
+    if(!$curr_tables{'CAT'}) {
+        $db->do(&Settings::createCATStmt);
         $changed = 1;
+    }else{
+        # If empty something happened to it. It shouldn't be EMPTY!
+        my @ret=Settings::selectRecords($db, "SELECT count(0) from CAT;")->fetchrow_array();
+        $changed = 1 if (!$ret[0]);
     }
     #Have cats been wiped out?
-    $st = $db->prepare('SELECT count(ID) FROM CAT;');
-    $st->execute();
-    if($st->fetchrow_array()==0) {
-         $changed = 1;
-    }
-
-    $st = $db->prepare(selSQLTbl('AUTH'));
-    $st->execute();
-    if(!$st->fetchrow_array()) {
-
-
-    my $stmt = qq(
-        CREATE TABLE AUTH(
-                alias varchar(20) PRIMARY KEY,
-                passw TEXT,
-                email varchar(44),
-                action TINY
-        ) WITHOUT ROWID;
-        CREATE INDEX idx_auth_name_passw ON AUTH (ALIAS, PASSW);
-        );
+    $changed = 1 if Settings::countRecordsIn($db, 'CAT') == 0;
 
+    #TODO Multiple cats per log future table.
+    if(!$curr_tables{'LOGCATSREF'}) {
+        $db->do(&Settings::createLOGCATSREFStmt);
+    }
 
-        $rv = $db->do($stmt);
-        if($rv < 0){print "<p>Error->"& $DBI::errstri &"</p>"};
-        $st = $db->prepare("SELECT ALIAS, PASSW, EMAIL, ACTION FROM AUTH WHERE alias='$alias' AND passw='$passw';");
-        $st->execute();
-        my @res = $st->fetchrow_array();
-        if(scalar @res == 0) {
-            $st = $db->prepare('INSERT INTO AUTH VALUES (?,?,?,?);');
-            $st->execute($alias, $passw,"",0);
-        }
+    if(!$curr_tables{'AUTH'}) {
+        $db->do(&Settings::createAUTHStmt);
+        my $st = $db->prepare('INSERT INTO AUTH VALUES (?,?,?,?);');
+           $st->execute($alias, $passw,"",0);
     }
     #
     # Scratch FTS4 implementation if present.
     #
-    $st = $db->prepare(selSQLTbl('NOTES_content'));
-    $st->execute();
-    if($st->fetchrow_array()) {
-        $rv = $db->do('DROP TABLE NOTES;');
-        if($rv < 0){print "<p>Error->"& $DBI::errstri &"</p>"};
+    if($curr_tables{'NOTES_content'}) {
+        $db->do('DROP TABLE NOTES;');
+        $db->do('DROP NOTES_content;');
+        $hasNotesTbl = 0;
     }
     #
     # New Implementation as of 1.5, cross SQLite Database compatible.
     #
-    $st = $db->prepare(selSQLTbl('NOTES'));
-    $st->execute();
-    if(!$st->fetchrow_array()) {
-        my $stmt = qq(
-            CREATE TABLE NOTES (LID PRIMARY KEY NOT NULL, DOC TEXT);
-        );
-        $rv = $db->do($stmt);
-        if($rv < 0){print "<p>Error->"& $DBI::errstri &"</p>"};
-    }
-
-
-    $st = $db->prepare(selSQLTbl('CONFIG'));
-    $st->execute();
-    if(!$st->fetchrow_array()) {
-        #v.1.3 -> v.1.4
-        #alter table CONFIG add DESCRIPTION VCHAR(128);
-    my $stmt = qq(
-                        CREATE TABLE CONFIG(
-                                ID TINY PRIMARY KEY NOT NULL,
-                                NAME VCHAR(16),
-                                VALUE VCHAR(28),
-                                DESCRIPTION VCHAR(128)
-                        );
-                        CREATE INDEX idx_config_name ON CONFIG (NAME);
-                );
-        $rv = $db->do($stmt);
-        $st->finish();
-        $changed = 1;
-
-    }
-    else{
-                #Has configuration been wiped out?
-                $st = $db->prepare('SELECT count(ID) FROM CONFIG;'); $st->execute();
-                $changed = 1 if(!$st->fetchrow_array());
-    }
-    #We got an db now, lets get settings from there.
-    Settings::getConfiguration($db);
-    if(!$changed){
-        #Run db fix renum if this is an relese update? Relese in software might not be what is in db, which counts.
-        #$st = Settings::dbExecute($db, 'SELECT NAME, VALUE FROM CONFIG WHERE NAME == "RELEASE_VER";');
-        $st    = $db->prepare('SELECT ID, NAME, VALUE FROM CONFIG WHERE NAME IS "RELEASE_VER";');
-        $st->execute() or die "<p>ERROR with->$DBI::errstri</p>";
-        my @pair = $st->fetchrow_array();
-        my $cmp = $pair[2] eq $RELEASE;
-        $debug .= "Upgrade cmp(RELESE_VER:'$pair[2]' eq Settings::release:'$RELEASE') ==  $cmp";
+    if(!$hasNotesTbl) {$db->do(&Settings::createNOTEStmt);}
+
+    if($changed){
+        #It is also good to run db fix (config page) to renum if this is an release update?
+        #Release in software might not be what is in db, which counts.
+        #This here next we now update.
+        my @r = Settings::selectRecords($db, 'SELECT ID, VALUE FROM CONFIG WHERE NAME IS "RELEASE_VER";')->fetchrow_array();
+        my $did = $r[0];
+        my $dnm = $r[1];
+        my $cmp = $dnm eq $RELEASE;
+        $debug .= "Upgrade cmp(RELESE_VER:'$dnm' eq Settings::release:'$RELEASE') ==  $cmp";
         #Settings::debug(1);
         if(!$cmp){
             Settings::renumerate($db);
@@ -329,74 +351,56 @@ try{
             #^REL_RENUM is marker that an renumeration is issued during upgrade.
             my $pv = &Settings::obtainProperty($db, '^REL_RENUM');
             if($pv){
-                $pv += 1;
+                $pv++;
             }
             else{
-                $pv = "1";
+                $pv = 0;
             }
             &Settings::configProperty($db, 200, '^REL_RENUM',$pv);
-            &Settings::configProperty($db, $pair[0], 'RELEASE_VER', $RELEASE);
-            &Settings::toLog($db,&dbTimeStamp, "Upgraded LifeLog from ".$pair[2]." to $RELEASE version, this is the $pv upgrade.");
-            &populate($db);
+            &Settings::configProperty($db, $did>0?$did:0, 'RELEASE_VER', $RELEASE);
+            &Settings::toLog($db, "Upgraded Life Log from v.$dnm to v.$RELEASE version, this is the $pv upgrade.") if $pv;
         }
-    }
-    else{
         &populate($db);
     }
+    Settings::toLog($db, "Log accessed by $alias.") if(&Settings::trackLogins);
     #
-     $db->disconnect();
+        $db->disconnect();
     #
     #Still going through checking tables and data, all above as we might have an version update in code.
     #Then we check if we are login in intereactively back. Interective, logout should bring us to the login screen.
     #Bypassing auto login. So to start maybe working on another database, and a new session.
     return $cgi->param('autologoff') == 1;
-}
- catch{
-    print $cgi->header;
-    print "<font color=red><b>SERVER ERROR</b></font>:".$_;
-    print $cgi->end_html;
-    exit;
- }
 
 }
-#TODO move this subroutine to settings.
-sub dbTimeStamp {
-    my $dat = DateTime->now;
-    $dat -> set_time_zone(Settings::timezone());
-    return DateTime::Format::SQLite->format_datetime($dat);
-}
+
 
 sub populate {
 
-        my $db = shift;
-        my ($did,$name, $value, $desc);
-        my $inData = 0;
-        my $err = "";
-        my %vars = ();
-        my @lines;
-        my $table_type = 0;
+    my $db = shift;
+    my ($did,$name, $value, $desc);
+    my $inData = 0;
+    my $err = "";
+    my %vars = ();
+    my @lines;
+    my $tt = 0;
 
-        open(my $fh, "<:perlio", &Settings::logPath.'main.cnf' ) or die "Can't open main.cnf: $!";
-        read $fh, my $content, -s $fh;
+    open(my $fh, "<:perlio", &Settings::logPath.'main.cnf' ) or LifeLogException->throw( "Can't open main.cnf: $!");
+    read $fh, my $content, -s $fh;
              @lines  = split '\n', $content;
-      close $fh;
-#TODO Check if script id is unique to database? If not script prevails to database entry.
-#So, if user settings from a previous release, must be migrated later.
-try{
+    close $fh;
 
-        my $insConfig = $db->prepare('INSERT INTO CONFIG VALUES (?,?,?,?)');
-        my $insCat    = $db->prepare('INSERT INTO CAT VALUES (?,?,?)');
-                        $db->begin_work();
+    my $insConfig = $db->prepare('INSERT INTO CONFIG VALUES (?,?,?,?)');
+    my $insCat    = $db->prepare('INSERT INTO CAT VALUES (?,?,?)');
+                    $db->begin_work();
     foreach my $line (@lines) {
 
-                    last if ($line =~ /<MIG<>/);
-                    my @tick = split("`",$line);
+                    last if ($line =~ /<MIG<>/);#Not doing it with CNF1.0
 
-                     if( index( $line, '<<CONFIG<' ) == 0 ){$table_type = 0; $inData = 0;}
-                    elsif( index( $line, '<<CAT<' ) == 0 )   {$table_type = 1; $inData = 0;}
-                    elsif( index( $line, '<<LOG<' ) == 0 )   {$table_type = 2; $inData = 0;}
-                    elsif( index( $line, '<<~MIG<>' ) == 0 ) {next;} #Migration is complex main.cnf contains though SQL alter statements.
+                       if( index( $line, '<<CONFIG<' ) == 0 )  {$tt = 0; $inData = 0;}
+                    elsif( index( $line, '<<CAT<'    ) == 0 )  {$tt = 1; $inData = 0;}
+                    elsif( index( $line, '<<LOG<'    ) == 0 )  {$tt = 2; $inData = 0;}
 
+                    my @tick = split("`",$line);
                     if( scalar @tick  == 2 ) {
 
                         my %hsh = $tick[0] =~ m[(\S+)\s*=\s*(\S+)]g;
@@ -425,23 +429,26 @@ $err .= "Invalid, spec'ed {uid}|{variable}`{description}-> $line\n";
 
                                                 }#rof
                                     }
-                                    elsif($table_type==0){
-                                                        $err .= "Invalid, spec'd entry -> $line\n";
-                                    }elsif($table_type==1){
-                                                            my @pair = $tick[0] =~ m[(\S+)\s*\|\s*(\S+)]g;
+                                     elsif($tt==0){
+$err .= "Invalid, spec'd entry -> $line\n";
+                                    }elsif($tt==1){
+                                                            my @pair = $tick[0] =~ m[(\S+)\s*\|\s*(\S+\s*\S*)]g;
                                                             if ( scalar(@pair)==2 ) {
-                                                                    my $st = $db->prepare("SELECT ID FROM CAT WHERE NAME LIKE '$pair[1]';");
-                                                                        $st->execute();
-                                                                        $inData = 1;
-                                                                        if(!$st->fetchrow_array()) {
-                                                                            $insCat->execute($pair[0],$pair[1],$tick[1]);
+                                                                        # In older DB versions the Category name could be different, user modified.
+                                                                        # The unique id and name interwined, changed. Hence we check on name first.
+                                                                        # Then check if the  ID is available. If not just skip, the import. Reseting can fix that latter.
+                                                                        if(!Settings::selectRecords($db, "SELECT ID FROM CAT WHERE NAME LIKE '$pair[1]';")->fetchrow_array()) {
+                                                                            if(!Settings::selectRecords($db, "SELECT ID FROM CAT WHERE ID = $pair[0];")->fetchrow_array()){
+                                                                               $insCat->execute($pair[0],$pair[1],$tick[1]);
+                                                                            }
                                                                         }
+                                                                        $inData = 1;
                                                             }
                                                             else {
 $err .= "Invalid, spec'ed {uid}|{category}`{description}-> $line\n";
                                                             }
-                                    }elsif($table_type==2){
-                                            #TODO Do we really want this?
+                                    }elsif($tt==2){
+                                            #TODO Do we really want this? Insert into log from config script.
                                     }
                     }elsif($inData && length($line)>0){
 
@@ -454,34 +461,51 @@ $err .= "Invalid, spec'ed {uid}|{category}`{description}-> $line\n";
 
                     }
         }
-        die "Configuration script ".&Settings::logPath."/main.cnf [$fh] contains errors." if $err;
-        $db->commit();
-    } catch{
-      print $cgi->header;
-      print "<font color=red><b>SERVER ERROR!</b></font><br> ".$_."<br><pre>$err</pre>";
-      print $cgi->end_html;
-      exit;
- }
+    LifeLogException->throw(error=>"Configuration script ".&Settings::logPath."/main.cnf [$fh] contains errors. Err:$err", show_trace=>1) if $err;
+    $db->commit();
 }
 
-sub selSQLTbl{
+sub selSQLTbl {
       my $name = $_[0];
 return "SELECT name FROM sqlite_master WHERE type='table' AND name='$name';"
 }
 
-sub selSQLView{
+sub selSQLView {
       my $name = $_[0];
 return "SELECT name FROM sqlite_master WHERE type='view' AND name='$name';"
 }
 
 
-sub logout{
+sub logout {
+
+    if(&Settings::trackLogins){
+    try{
+        $alias = $session->param('alias');
+        $passw = $session->param('passw');
+        my $database = &Settings::logPath.'data_'.$alias.'_log.db';
+        my $dsn= "DBI:SQLite:dbname=$database";
+        my $db = DBI->connect($dsn, $alias, $passw, { RaiseError => 1 })
+                    or LifeLogException->throw($DBI::errstri);
+        Settings::toLog($db, "Log properly loged out by $alias.");
+        $db->disconnect();
+    }catch{
+        my $err = $@;
+        my $dbg = "" ;
+        my $pwd = `pwd`;
+        $pwd =~ s/\s*$//;
+        $dbg = "--DEBUG OUTPUT--\n$debug" if $debug;
+        print $cgi->header,
+        "<font color=red><b>SERVER ERROR</b></font> on ".DateTime->now.
+        "<pre>".$pwd."/$0 -> &".caller." -> [$err]","\n$dbg</pre>",
+        $cgi->end_html;
+        exit;
+    }
+    }
+
 
-    $session->delete();
-    $session->flush();
     print $cgi->header(-expires=>"0s", -charset=>"UTF-8", -cookie=>$cookie);
     print $cgi->start_html(-title => "Personal Log Login", -BGCOLOR=>"black",
-                             -style =>{-type => 'text/css', -src => 'wsrc/main.css'},
+                           -style =>{-type => 'text/css', -src => 'wsrc/main.css'},
             );
 
     print qq(<font color="white"><center><h2>You have properly loged out of the Life Log Application!</h2>
@@ -496,6 +520,11 @@ sub logout{
     );
 
     print $cgi->end_html;
+
+    $session->delete();
+    $session->flush();
+
+
     exit;
 }
 
index 91595916d9b735e2a5a16eeebb2ec7bc20aa3bbe..371d2afb3d64d1fa2d504167557cb657f0c2ba16 100755 (executable)
@@ -5,7 +5,8 @@
 #
 use warnings;
 use strict;
-use Try::Tiny;
+use Exception::Class ('LifeLogException');
+use Syntax::Keyword::Try;
 use Switch;
 
 use CGI;
@@ -21,13 +22,14 @@ use Date::Parse;
 use Time::localtime;
 
 use Regexp::Common qw /URI/;
+use List::MoreUtils qw(uniq);
 
 #DEFAULT SETTINGS HERE!
 use lib "system/modules";
 require Settings;
 
 my $cgi = CGI->new;
-my $sss = new CGI::Session( "driver:File", $cgi, { Directory => Settings::logPath() } );
+my $sss = new CGI::Session( "driver:File", $cgi, { Directory => &Settings::logPath } );
 my $sid      = $sss->id();
 my $dbname   = $sss->param('database');
 my $userid   = $sss->param('alias');
@@ -40,11 +42,10 @@ if ( !$userid || !$dbname ) {
     exit;
 }
 
-my $database = Settings::logPath() . $dbname;
+my $database = &Settings::logPath . $dbname;
 my $dsn      = "DBI:SQLite:dbname=$database";
 my $db       = DBI->connect( $dsn, $userid, $password, { PrintError => 0, RaiseError => 1 } )
-  or die "<p>Error->" & $DBI::errstri & "</p>";
-
+                      or LifeLogException->throw("Connection failed [$DBI::errstri]");
 my ( $imgw, $imgh );
 #Fetch settings
  Settings::getConfiguration($db);
@@ -55,18 +56,17 @@ my ( $imgw, $imgh );
 my $log_rc      = 0;
 my $log_rc_prev = 0;
 my $log_cur_id  = 0;
-my $log_top = 0;
+my $log_top     = 0;
 my $rs_keys     = $cgi->param('keywords');
-my $rs_cat_idx  = $cgi->param('category');
 my $prm_vc      = $cgi->param("vc");
 my $prm_xc      = $cgi->param("xc");
-my $prm_xc_lst  = $cgi->param("idx_cat_x");
+my $prm_xc_lst  = $cgi->param("xclst");
 my $rs_dat_from = $cgi->param('v_from');
 my $rs_dat_to   = $cgi->param('v_to');
 my $rs_prev     = $cgi->param('rs_prev');
 my $rs_cur      = $cgi->param('rs_cur');
 my $rs_page     = $cgi->param('rs_page');
-my $stmS        = 'SELECT ID, ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY from VW_LOG WHERE';
+my $stmS        = 'SELECT PID, ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY from VW_LOG WHERE';
 my $stmE        = "";
 my $stmD        = "";
 my $sm_reset_all;
@@ -84,8 +84,9 @@ my $lang  = Date::Language->new(Settings::language());
 my $today = DateTime->now;
    $today -> set_time_zone(Settings::timezone());
 
-#Excludes can be now set as permanent to page view excluded, visible if view searched.
+#Excludes can be now be set as permanent to page view excluded, visible if view searched.
 #http://localhost:8080/cgi-bin/main.cgi?vc=0&category=0&xc=0&idx_cat_x=0&v_from=&v_to=&keywords=&srch_reset=0
+
 if(!$prm_vc && &Settings::keepExcludes){
     if($prm_xc_lst){
         &Settings::configProperty($db, 201, '^EXCLUDES', $prm_xc_lst);
@@ -108,7 +109,7 @@ if ( $rs_dat_from && $rs_dat_to ) {
 
 #Toggle if search deployed.
 my $toggle = "";
-if ( $rs_keys || $rs_cat_idx || $stmD || $prm_vc > 0 || $prm_xc > 0) { $toggle = 1; }
+if ( $rs_keys || $stmD || $prm_vc > 0 || $prm_xc > 0) { $toggle = 1; }
 
 
 ##Handle Session Keeps
@@ -124,28 +125,43 @@ if($cgi->param('srch_reset') == 1){
    $sss->clear('sss_xc');
 }
 
-if($prm_vc){
-   if ($cgi->param('sss_xc') eq 'on'){
-       $sss->param('sss_vc', $prm_vc)
-   }
-   else{
-        $sss->clear('sss_vc');
-   }
-}else{
-       $prm_vc = $sss->param('sss_vc');
-}
-if($prm_xc){
+
+if($prm_xc &&$prm_xc ne ""){
+#TODO (2020-02-23) It gets too complicated. should not have both $prm_xc and $prm_xc_lst;
+       $prm_xc =~ s/^0*//g;$prm_xc_lst=~ s/^\,$//g;
+       if(!$prm_xc_lst||$prm_xc_lst==0){#} && index($prm_xc, ',') > 0){
+           $prm_xc_lst =  $prm_xc;
+       }else{
+            my $f;
+            my @xc_lst = split /\,/, $prm_xc_lst; @xc_lst = uniq(sort { $a <=> $b }  @xc_lst);
+            foreach my $n(@xc_lst){
+                if($n == $prm_xc){ $f=1; last; }
+            }
+            if(!$f){#not found view was clicked changing category but not adding it to ex list. Let's add it to the list.
+                $prm_xc_lst .= ",$prm_xc";
+            }
+            $prm_xc_lst=~ s/\,$//g;$prm_xc_lst=~ s/\,\,/\,/g;
+       }
+
+
    if ($cgi->param('sss_xc') eq 'on'){
-       $sss->param('sss_xc', $prm_xc)
+       $sss->param('sss_xc', $prm_xc);
+       $sss->param('sss_xc_lst', $prm_xc_lst);
    }
    else{
         $sss->clear('sss_xc');
+        $sss->clear('sss_xc_lst');
    }
+
+
 }else{
        $prm_xc = $sss->param('sss_xc');
+       $prm_xc_lst = $sss->param('sss_xc_lst');
 }
 
-my @xc_lst = split /\,/, $prm_xc_lst;
+
+##
+my @xc_lst = split /\,/, $prm_xc_lst; @xc_lst = uniq(sort { $a <=> $b }  @xc_lst);
 
 
 $sss->flush();
@@ -183,6 +199,7 @@ print $cgi->start_html(
         { -type => 'text/css', -src => 'wsrc/quill/katex.min.css' },
         { -type => 'text/css', -src => 'wsrc/quill/monokai-sublime.min.css' },
         { -type => 'text/css', -src => 'wsrc/quill/quill.snow.css' },
+        { -type => 'text/css', -src => 'wsrc/jquery.sweet-dropdown.css' },
 
     ],
     -script => [
@@ -205,69 +222,57 @@ print $cgi->start_html(
         { -type => 'text/javascript', -src => 'wsrc/jscolor.js' },
         { -type => 'text/javascript', -src => 'wsrc/moment.js' },
         { -type => 'text/javascript', -src => 'wsrc/moment-timezone-with-data.js' },
+        { -type => 'text/javascript', -src => 'wsrc/jquery.sweet-dropdown.js'}
 
     ],
 );
 
-my $rv;
+
 my $st;
-my $stmtCat = "SELECT ID, NAME, DESCRIPTION FROM CAT ORDER BY ID;";
-my $stmt    = "SELECT ID, ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY FROM VW_LOG WHERE STICKY = 1;";
+my $sqlCAT = "SELECT ID, NAME, DESCRIPTION FROM CAT ORDER BY ID;";
+my $sqlVWL = "SELECT ID, ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY FROM VW_LOG WHERE STICKY = 1 LIMIT ".&Settings::viewAllLimit.";";
 
-print qq("## Using db -> $dsn) if $DEBUG;
+print qq(## Using db -> $dsn\n) if $DEBUG;
 
-$st = $db->prepare($stmtCat);
-$rv = $st->execute() or die "<p>Error->" & $DBI::errstri & "</p>";
+$st = $db->prepare($sqlCAT);
+$st->execute() or LifeLogException->throw($DBI::errstri);
 
-my $cats = qq(<select   class="ui-widget-content" id="ec" name="ec"
- onFocus="show('#cat_desc');"
- onBlur="helpSelCategory(this);"
- onScroll="helpSelCategory(this);updateSelCategory(this)"
- onChange="updateSelCategory(this)">
- <option value="0">---</option>\n);
 my %hshCats;
 my %hshDesc = {};
 my $c_sel   = 1;
-my $cats_v  = $cats;
-my $cats_x  = $cats;
-my $cat_desc = "";
-$cats_v =~ s/\"ec\"/\"vc\"/g;
-$cats_x =~ s/\"ec\"/\"xc\"/g;
+my $data_cats = "";
+my $td_cat = "<tr><td><ul>";
+my $td_itm_cnt =0;
 while ( my @row = $st->fetchrow_array() ) {
-    if ( $row[0] == $c_sel ) {
-        $cats .= qq(<option selected value="$row[0]">$row[1]</option>\n);
+    my $n = $row[1];
+    $n =~ s/\s*$//g;
+    $hshCats{$row[0]} = $n;
+    $hshDesc{$row[0]} = $row[2];
+    if($td_itm_cnt>4){
+        $td_cat .= "</ul></td><td><ul>";
+        $td_itm_cnt = 0;
     }
-    else {
-        $cats .= qq(<option value="$row[0]">$row[1]</option>\n);
-    }
-    if ( $row[0] == $prm_vc ) {
-        $cats_v .= qq(<option selected value="$row[0]">$row[1]</option>\n);
-    }
-    else {
-        $cats_v .= qq(<option value="$row[0]">$row[1]</option>\n);
-    }
-    if ( $row[0] == $prm_xc ) {
-        $cats_x .= qq(<option selected value="$row[0]">$row[1]</option>\n);
-    }
-    else {
-        $cats_x .= qq(<option value="$row[0]">$row[1]</option>\n);
+    $td_cat .= "<li id='$row[0]'><a href='#'>$row[1]</a></li>";
+    $td_itm_cnt++;
+
+}
+if($td_itm_cnt<5){#fill spacing.
+    for (my $i=0;$i<5-$td_itm_cnt;$i++){
+        $td_cat .= "<li><a href='#'></a>&nbsp;</li>";
     }
-    $hshCats{ $row[0] } = $row[1];
-    $hshDesc{ $row[0] } = $row[2];
 }
+$td_cat .= "</ul></td></tr>";
 
-$cats .= '</select>';
-$cats_v .= '</select>';
-$cats_x .= '</select>';
 
 for my $key ( keys %hshDesc ) {
     my $kv = $hshDesc{$key};
-    if ( $kv ne ".." ) {
-        $cat_desc .= qq(<li id="$key">$kv</li>\n);
+    if ( $kv ne ".." && index($key,'HASH(0x')!=0) {
+        my $n = $hshCats{$key};
+        $data_cats .= qq(<meta id="cats[$key]" name="$n" content="$kv">\n);
     }
 }
 my $log_output =
-qq(<form id="frm_log" action="remove.cgi" onSubmit="return formDelValidation();">
+qq(<form id="frm_log" action="data.cgi" onSubmit="return formDelValidation();">
 <TABLE class="tbl" border="0" width=").&Settings::pagePrcWidth.qq(%">
 <tr class="r0">
        <th>Date</th>
@@ -277,15 +282,12 @@ qq(<form id="frm_log" action="remove.cgi" onSubmit="return formDelValidation();"
     <th>Edit</th>
 </tr>);
 
-    if ( defined $prm_vc ) {    #view category form selection
-        $rs_cat_idx = $prm_vc;
-    }
 
     if ( $rs_keys && $rs_keys ne '*' ) {
 
         my @keywords = split / /, $rs_keys;
-        if ($rs_cat_idx && $rs_cat_idx != $prm_xc) {
-            $stmS .= " ID_CAT='" . $rs_cat_idx . "' AND";
+        if ($prm_vc && $prm_vc != $prm_xc) {
+            $stmS .= " ID_CAT='" . $prm_vc . "' AND";
         }
         else {
             if($prm_xc>0){
@@ -308,16 +310,16 @@ qq(<form id="frm_log" action="remove.cgi" onSubmit="return formDelValidation();"
                     $stmS = $stmS . " OR ";
                 }
             }
-            $stmt = $stmS . $stmE;
+            $sqlVWL = $stmS . $stmE;
         }
     }
-    elsif ($rs_cat_idx && $rs_cat_idx != $prm_xc) {
+    elsif ($prm_vc && $prm_vc != $prm_xc) {
 
         if ($stmD) {
-            $stmt = $stmS . $stmD . " AND ID_CAT='" . $rs_cat_idx . "'" . $stmE;
+            $sqlVWL = $stmS . $stmD . " AND ID_CAT=" . $prm_vc . $stmE;
         }
         else {
-            $stmt = $stmS . " ID_CAT='" . $rs_cat_idx . "'" . $stmE;
+            $sqlVWL = $stmS . " ID_CAT=" . $prm_vc . ";" . $stmE;
         }
     }
     else {
@@ -329,39 +331,39 @@ qq(<form id="frm_log" action="remove.cgi" onSubmit="return formDelValidation();"
                             $ands .= " ID_CAT!=$_ AND";
                     }
                     $ands =~ s/AND$//g;
-                    $stmt = $stmS . $ands . $stmE;
+                    $sqlVWL = $stmS . $ands . $stmE;
                 }
                 else{
-                    $stmt = $stmS . " ID_CAT!=$prm_xc" . $stmE;
+                    $sqlVWL = $stmS . " ID_CAT!=$prm_xc;" . $stmE;
                 }
 
 
 
         }
         if ($stmD) {
-            $stmt = $stmS . $stmD . $stmE;
+            $sqlVWL = $stmS . $stmD . $stmE;
         }
     }
 
-    ###################
-      &processSubmit;
-    ###################
+###################
+    &processSubmit;
+###################
 
     my $tfId      = 0;
     my $id        = 0;
-    my $log_start = index $stmt, "<=";
+    my $log_start = index $sqlVWL, "<=";
     my $re_a_tag  = qr/<a\s+.*?>.*<\/a>/si;
+    #TODO implement isView instead of quering params over and over again.
+    my $isView = rindex ($sqlVWL, 'PID<=') > 0 || rindex ($sqlVWL, 'ID_CAT=') > 0;
 
-    print $cgi->pre("###[Session PARAMS->vc=$prm_vc|xc=$prm_xc|xc_lst=@xc_lst|keepExcludes=".&Settings::keepExcludes."] -> ".$stmt) if $DEBUG;
+    print $cgi->pre("###[Session PARAMS->isV:$isView|vc=$prm_vc|xc=$prm_xc|xc_lst=$prm_xc_lst|\@xc_lst=@xc_lst|keepExcludes=".&Settings::keepExcludes."] -> ".$sqlVWL) if $DEBUG;
 
     if ( $log_start > 0 ) {
 
         #check if we are at the beggining of the LOG table?
-        my $stc =
-          $db->prepare('SELECT PID from VW_LOG LIMIT 1;');
-        $stc->execute();
+        my $stc = traceDBExe('SELECT PID from VW_LOG LIMIT 1;');
         my @row = $stc->fetchrow_array();
-        $log_top = $row[0];
+            $log_top = $row[0];
         if ($log_top == $rs_prev && $rs_cur == $rs_prev ) {
             $log_start = -1;
         }
@@ -375,41 +377,45 @@ qq(<form id="frm_log" action="remove.cgi" onSubmit="return formDelValidation();"
     my $sum       = 0;
     my $exp       = 0;
     my $ass       = 0;
-    $st = $db->prepare($stmt);
-    $rv = $st->execute() or die "<p>Error->" & $DBI::errstri & "</p>";
-    if ( $rv < 0 ) {
-        print "<p>Error->" & $DBI::errstri & "</p>";
-    }
-
-    &buildLog;
 
 
-    if(index ($stmt, 'PID <=') < 1 && !$prm_vc  && !$prm_xc && !$rs_keys && !$rs_dat_from){
+    #place sticky or view param.ed entries first!
+    buildLog(traceDBExe($sqlVWL));
+    #Following is saying is in page selection, not view selection, or accounting on type of sticky entries.
+    if( !$isView && !$prm_vc  && !$prm_xc && !$rs_keys && !$rs_dat_from ){
+        $sqlVWL = "SELECT ID, ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY FROM VW_LOG WHERE STICKY != 1 LIMIT ".&Settings::viewAllLimit.";";
+        print $cgi->pre("###2 -> ".$sqlVWL)  if $DEBUG;
+        ;
+        &buildLog(traceDBExe($sqlVWL));
+    }
 
-        $stmt = "SELECT PID, ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY FROM VW_LOG WHERE STICKY != 1;";
-        print $cgi->pre("###2 -> ".$stmt)  if $DEBUG;
-        $st = $db->prepare($stmt);
-        $rv = $st->execute() or die or die "<p>Error->" & $DBI::errstri & "</p>";
-        if ( $rv < 0 ) {
-            print "<p>Error->" & $DBI::errstri & "</p>";
-        }
 
-        &buildLog;
+sub traceDBExe {
+    my $sql = shift;
+    try{
+        print "do:$sql" if ($DEBUG);
+        my $st = $db->prepare($sql);
+           $st -> execute() or LifeLogException->throw("Execute failed [$DBI::errstri]", show_trace=>1);
+        return $st;
+    }catch{
+                LifeLogException->throw(error=>"database error encountered.", show_trace=>1);
     }
+}
 
 sub buildLog {
-
-    while ( my @row = $st->fetchrow_array() ) {
-
-        $id = $row[0];# PID
-
-        my $ct  = $hshCats{$row[1]}; #ID_CAT
-        my $dt  = DateTime::Format::SQLite->parse_datetime( $row[2] );
-        my $log = $row[3];
-        my $am  = $row[4];
-        my $af  = $row[5]; #AFLAG -> Asset as 0, Income as 1, Expense as 2
-        my $rtf = $row[6]; #RTF has document true or false
-        my $sticky = $row[7]; #Sticky to top
+    my $pst = shift;
+    #print "## sqlVWL: $sqlVWL\n";
+    while ( my @row = $pst->fetchrow_array() ) {
+        my $i = 0;
+        $id = $row[$i++]; #ID must be rowid in LOG.
+        my $cid = $row[$i++]; #CID ID_CAT not used.
+        my $ct  = $hshCats{$cid}; #ID_CAT
+        my $rtf = $row[$i++];           #ID_RTF since v.1.8
+        my $dt  = DateTime::Format::SQLite->parse_datetime( $row[$i++] ); #LOG.DATE
+        my $log = $row[$i++]; #LOG.LOG
+        my $am  = $row[$i++]; #LOG.AMOUNT
+        my $af  = $row[$i++]; #AFLAG -> Asset as 0, Income as 1, Expense as 2
+        my $sticky = $row[$i++]; #Sticky to top
 
         if ( $af == 1 ) { #AFLAG Income
             $sum += $am;
@@ -436,7 +442,7 @@ sub buildLog {
         if ( $log_rc_prev == 0 ) {
             $log_rc_prev = $id;
         }
-        if ( $tfId == 1 ) {
+        if ( $tfId > 0) {
             $tfId = 0;
         }
         else {
@@ -454,7 +460,7 @@ sub buildLog {
             $sub = substr( $log, $idx + 1, $len - $idx - 1 );
             my $url = qq(<a href="$sub" target=_blank>$sub</a>);
             $tagged = 1;
-            $log =~ s/<<LNK<(.*?)>/$url/osi;
+            $log =~ s/<<LNK<(.*?)>+/$url/osi;
         }
 
         if ( $log =~ /<<IMG</ ) {
@@ -463,7 +469,7 @@ sub buildLog {
             $sub = substr( $log, $idx + 1, $len - $idx - 1 );
             my $url = qq(<img src="$sub"/>);
             $tagged = 1;
-            $log =~ s/<<IMG<(.*?)>/$url/osi;
+            $log =~ s/<<IMG<(.*?)>+/$url/osi;
         }
         elsif ( $log =~ /<<FRM</ ) {
             my $idx = $-[0] + 5;
@@ -485,10 +491,9 @@ sub buildLog {
             }
             else {
                 #TODO fetch from web locally the original image.
-                $lnk =
-qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
+                $lnk =qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
             }
-            $log =~ s/<<FRM<(.*?)>/$lnk/o;
+            $log =~ s/<<FRM<(.*?)>+/$lnk/o;
             $tagged = 1;
         }
 
@@ -527,7 +532,7 @@ qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
             my $idx = $-[0];
             my $len = index( $log, '>', $idx ) - 4;
             my $sub = "<b>" . substr( $log, $idx + 4, $len - $idx ) . "</b>";
-            $log =~ s/<<B<(.*?)>/$sub/o;
+            $log =~ s/<<B<(.*?)>+/$sub/o;
             $tagged = 1;
         }
         while ( $log =~ /<<I</ ) {
@@ -535,7 +540,7 @@ qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
             my $len = index( $log, '>', $idx ) - 4;
             last if $len<6;
             my $sub = "<i>" . substr( $log, $idx + 4, $len - $idx ) . "</i>";
-            $log =~ s/<<I<(.*?)>/$sub/o;
+            $log =~ s/<<I<(.*?)>+/$sub/o;
             $tagged = 1;
         }
         while ( $log =~ /<<TITLE</ ) {
@@ -543,7 +548,7 @@ qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
             my $len = index( $log, '>', $idx ) - 8;
             last if $len<9;
             my $sub = "<h3>" . substr( $log, $idx + 8, $len - $idx ) . "</h3>";
-            $log =~ s/<<TITLE<(.*?)>/$sub/o;
+            $log =~ s/<<TITLE<(.*?)>+/$sub/o;
             $tagged = 1;
         }
 
@@ -623,9 +628,13 @@ qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
         }
 
         my $ssymb = "Edit";
-        $ssymb = "Edit &#10037;" if $sticky;
+        my $ssid  = $tfId;
+        if ($sticky){
+            $ssymb = "Edit &#10037;";
+            $ssid = $tfId + 2;
+        }
 
-        $log_output .= qq(<tr class="r$tfId">
+        $log_output .= qq(<tr class="r$ssid">
                <td width="15%">$dtf<input id="y$id" type="hidden" value="$dty"/></td>
                <td id="t$id" width="10%" class="tbl">$dth</td>
                <td id="v$id" class="log" width="40%">$log</td>
@@ -680,13 +689,9 @@ qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
     ##
     #Fetch Keywords autocomplete we go by words larger then three.
     #
-    $st = $db->prepare( 'select LOG from LOG' . $stmE );
     my $aw_cnt    = 0;
     my $autowords = qq("gas","money","today");
-    $rv = $st->execute() or die or die "<p>Error->" & $DBI::errstri & "</p>";
-    if ( $rv < 0 ) {
-        print "<p>Error->" & $DBI::errstri & "</p>";
-    }
+
     &fetchAutocomplete;
 
     if ( $log_rc == 0 ) {
@@ -697,8 +702,8 @@ qq(\n<img src="$lnk" width="$imgw" height="$imgh" class="tag_FRM"/>);
         }
         elsif ($rs_keys) {
             my $criter = "";
-            if ( $rs_cat_idx > 0 ) {
-                $criter = "->Criteria[" . $hshCats{$rs_cat_idx} . "]";
+            if ( $prm_vc > 0 ) {
+                $criter = "->Criteria[" . $hshCats{$prm_vc} . "]";
             }
             $log_output .= qq(<tr><td colspan="5">
                        <b>Search Failed to Retrive any records on keywords: [<i>$rs_keys</i>]$criter!</b></td></tr>);
@@ -749,10 +754,20 @@ _TXT
       . $today->ymd . " " . $today->hms . qq(">
 
        &nbsp;<button type="button" onclick="return setNow();">Now</button>
-                       &nbsp;<button type="reset"  onclick="setNow();resetDoc(); return true;">Reset</button></td>
-                       <td style="text-align:top; vertical-align:top">Category:
-    $cats
-                               <br><br><div id="cat_desc" name="cat_desc"></div>
+                       &nbsp;<button type="reset"  onclick="setNow();resetDoc(); return true;">Reset</button>
+
+
+                <span id="cat_desc" name="cat_desc">Enter log...</span>
+
+            </td>
+                       <td style="text-align:top; vertical-align:top">Category:&nbsp;
+            <span id="lcat" class="span_cat">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i><font size=1>--Select --</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</i></span>
+                <button class="bordered" data-dropdown="#dropdown-standard">&#171;</button>
+
+            <div class="dropdown-menu dropdown-anchor-top-right dropdown-has-anchor" id="dropdown-standard">
+                        <table class="tbl">$td_cat</table>
+            </div>
+<!-- OLD -> \$cats was here -->
                        </td>
        </tr>
        <tr class="collpsd"><td style="text-align:right; vertical-align:top">Log:</td>
@@ -779,6 +794,7 @@ _TXT
        </tr>
        <tr class="collpsd"><td colspan="3"></td></tr>
        </table>
+    <input type="hidden" name="ec" id="ec" value="0"/>
        <input type="hidden" name="submit_is_edit" id="submit_is_edit" value="0"/>
        <input type="hidden" name="submit_is_view" id="submit_is_view" value="0"/>
        <input type="hidden" name="rs_all" value="0"/>
@@ -799,35 +815,74 @@ _TXT
             <a id="srch_close" href="#" onclick="return toggle('#div_srh .collpsd');">$sp2</a>
         </td>
       </tr>
-);
+    );
     my $sss_checked = 'checked' if &isInViewMode;
-    my $divxc = '<td id="divxc_lbl" align="right" style="display:none"><b>Excludes:</b></td><td align="left" id="divxc"></td>';
+    my $tdivxc = '<td id="divxc_lbl" align="right" style="display:none"><b>Excludes:</b></td><td align="left" id="divxc"></td>';
+    my $catselected  = '<i>&nbsp;&nbsp;&nbsp;<font size=1>-- Select --</font>&nbsp;&nbsp;&nbsp;</i>';
+    my $xcatselected = '<i>&nbsp;&nbsp;&nbsp;<font size=1>-- Select --</font>&nbsp;&nbsp;&nbsp;</i>';
+    my $xc_lst = '';
+    if($prm_vc){
+        $catselected = $hshCats{$prm_vc};
+         my $n = 16 - length($catselected);
+        $catselected =~ s/^(.*)/'&nbsp;' x $n . $1/e;
+    }
+
     if(@xc_lst){#Do list of excludes, past from browser in form of category id's.
         my $xcls ="";
-        foreach(@xc_lst){ $xcls .= $hshCats{$_}.','}
-        $xcls =~ s/\,$//g;
-        $divxc = '<td id="divxc_lbl" align="right"><b>Excludes:</b></td><td align="left" id="divxc">'.$xcls.'</td>';
+        foreach(@xc_lst){ $xcls .= $hshCats{$_}.',';$xc_lst.=$_.','}
+        $xcls =~ s/\,$//g; $xcls =~ s/\,\,/\,/g; $xc_lst=~ s/^0\,$//g;
+        $xcatselected = $hshCats{$prm_xc};
+        my $n = 16 - length($xcatselected);
+        $xcatselected =~ s/^(.*)/'&nbsp;' x $n . $1/e;
+        $tdivxc = '<td id="divxc_lbl" align="right"><b>Excludes:</b></td><td align="left" id="divxc">'.$xcls.'</td>';
+    }
+    elsif($prm_xc){
+        $xcatselected = $hshCats{$prm_xc};
+         my $n = 16 - length($xcatselected);
+        $xcatselected =~ s/^(.*)/'&nbsp;' x $n . $1/e;
+        $tdivxc = '<td id="divxc_lbl" align="right"><b>Excludes:</b></td><td align="left" id="divxc">'.$hshCats{$prm_xc}.'</td>';
     }
     $srh .=
     qq(
     <tr class="collpsd">
      <td align="right"><b>View by Category:</b></td>
-     <td align="left">$cats_v&nbsp;&nbsp;
-        <button id="btn_cat" onclick="viewByCategory(this);">View</button>
-        <input id="idx_cat" name="category" type="hidden" value="0"/>
+     <td align="left">
+
+             <span id="lcat_v" class="span_cat">$catselected</span>
+             <button class="bordered" data-dropdown="#dropdown-standard-v">&#171;</button>
+
+            <div id="dropdown-standard-v" class="dropdown-menu        dropdown-anchor-left-center      dropdown-has-anchor">
+                        <table class="tbl">$td_cat</table>
+            </div>
+
+            <input id="vc" name="vc" type="hidden" value="$prm_vc"/>
+            <button id="btn_cat" onclick="viewByCategory(this);">View</button>
      </td>
    </tr>
    <tr class="collpsd">
      <td align="right"><b>Exclude Category:</b></td>
-     <td align="left">$cats_x&nbsp;&nbsp;
-        <input id="idx_cat_x" name="idx_cat_x" type="hidden" value="0"/>
+     <td align="left">
+
+                 <span id="lcat_x" class="span_cat">$xcatselected</span>
+                 <button class="bordered" data-dropdown="#dropdown-standard-x">&#171;</button>
+
+            <div id="dropdown-standard-x" class="dropdown-menu        dropdown-anchor-left-center      dropdown-has-anchor">
+                        <table class="tbl">$td_cat</table>
+            </div>
+
+     <!-- \$cats_x&nbsp;&nbsp;   -->
+
+        <input id="xc" name="xc" type="hidden" value="$prm_xc"/>
+        <input id="xclst" name="xclst" type="hidden" value="$xc_lst"/>
+
         <button id="btnxca" onClick="return addExclude()"/>Add</button>&nbsp;&nbsp;
-        <button id="btnxrc" type="button" onClick="return removeExclude()">Remove</button>&nbsp;&nbsp;
+        <button id="btnxrc" type="button" onClick="return removeExclude()">Remove</button>&nbsp;
+        <button id="btnxrc" type="button" onClick="return resetExclude()">Reset</button>&nbsp;&nbsp;&nbsp;
         <button id="btn_cat" onclick="return viewExcludeCategory(this);">View</button>&nbsp;&nbsp;
         <input id="sss_xc" name="sss_xc" type="checkbox" $sss_checked/> Keep In Seession
      </td>
    </tr>
-   <tr class="collpsd">$divxc</tr>
+   <tr class="collpsd">$tdivxc</tr>
    <tr class="collpsd">
     <td align="right"><b>View by Date:</b></td>
        <td align="left">
@@ -844,7 +899,7 @@ _TXT
     </td>
     );
 
-    if ( ( $rs_keys && $rs_keys ne '*' ) || $rs_cat_idx || $stmD || $prm_xc ) {
+    if ( ( $rs_keys && $rs_keys ne '*' ) || $prm_vc || $stmD || $prm_xc ) {
         $sm_reset_all = '<a class="a_" onclick="resetView();">Reset View</a><hr>';
         $srh .= '<tr class="collpsd"><td align="right" colspan="2">
         <input id="srch_reset" name="srch_reset" type="hidden" value="0"/>
@@ -860,8 +915,8 @@ _TXT
  #   Page printout from here!   #
 ################################
 
-
-print qq(<div id="menu" title="To close this menu click on its heart, and wait.">
+print qq(
+<div id="menu" title="To close this menu click on its heart, and wait.">
 <div class="hdr" style="marging=0;padding:0px;">
 <a id="to_top" href="#top" title="Go to top of page."><span class="ui-icon ui-icon-arrowthick-1-n" style="float:none;"></span></a>&nbsp;
 <a id="to_bottom" href="#bottom" title="Go to bottom of page."><span class="ui-icon ui-icon-arrowthick-1-s" style="float:none;"></span></a>
@@ -884,7 +939,7 @@ $sm_reset_all
 <a class="a_" href="login_ctr.cgi?logout=bye">LOGOUT</a><br>
 <span style="font-size: x-small;">$vmode</span><br>
 </div>
-         <div id="div_log">$frm</div>\n
+         <div id="div_log">$frm</div>
          <div id="div_srh">$srh</div>
       $quill
       <div id="div_hlp">$help</div>
@@ -892,9 +947,12 @@ $sm_reset_all
          <div><a class="a_" href="stats.cgi">View Statistics</a></div><br>
          <div><a class="a_" href="config.cgi">Configure Log</a></div><hr>
          <div><a class="a_" href="login_ctr.cgi?logout=bye">LOGOUT</a><hr><a name="bottom"/></div>
-<ul id="cat_lst">
-       $cat_desc
-</ul>
+<!-- Cat Data Start-->
+<span id="meta_cats">
+       $data_cats
+</span>
+<!--Cat Data End->
+<!-- Page Settings Specifics date:20200222 -->
 <script type="text/javascript">
     \$( function() {
         var tags = [$autowords];
@@ -905,6 +963,7 @@ $sm_reset_all
 </script>
 );
 
+
 print $cgi->end_html;
 $st->finish;
 $db->disconnect();
@@ -912,6 +971,15 @@ undef($sss);
 exit;
 
 
+# http://localhost:8080/cgi-bin/main.cgi?
+# date=2020-02-27+11%3A05%3A28&
+# log=new
+# &am=
+# &amf=0
+# &ec=92
+
+
+# &submit_is_edit=0&submit_is_view=0&rs_all=0&rs_cur=0&rs_prev=332
 
 
 sub processSubmit {
@@ -919,7 +987,7 @@ sub processSubmit {
         my $date = $cgi->param('date');
         my $log  = $cgi->param('log');
         my $cat  = $cgi->param('ec');
-        my $cnt;
+        my $cnt ="";
         my $am = $cgi->param('am');
         my $af = $cgi->param('amf');
 
@@ -928,8 +996,9 @@ sub processSubmit {
         my $view_all  = $cgi->param('rs_all');
         my $rtf    = $cgi->param('rtf');
         my $sticky = $cgi->param('sticky');
+        my $stm;
 
-
+        ##TODO
         if($rtf eq 'on'){$rtf = 1}  else {$rtf = 0}
         if($sticky eq 'on'){$sticky = 1} else {$sticky = 0}
         if(!$am){$am=0}
@@ -942,25 +1011,24 @@ try {
 
                 #Update
                 $date = DateTime::Format::SQLite->parse_datetime($date);
-                my $stm = qq( UPDATE LOG SET ID_CAT='$cat',
+                $stm = qq( UPDATE LOG SET ID_CAT='$cat', ID_RTF='$rtf',
                                              DATE='$date',
                                              LOG='$log',
-                                             AMOUNT='$am',
-                                             AFLAG = '$af',
-                                             RTF='$rtf',
-                                             STICKY='$sticky' WHERE rowid="$edit_mode";);
+                                             AMOUNT=$am,
+                                             AFLAG = $af,
+                                             STICKY=$sticky WHERE rowid="$edit_mode";
+                    <br>);
                 #
                 print $stm if $DEBUG;
                 #
 
-                my $dbUpd = DBI->connect( $dsn, $userid, $password, { RaiseError => 1 } )  or die "<p>Error->" & $DBI::errstri & "</p>";
-                my $st = $dbUpd->prepare($stm);
-                   $st->execute();
+                my $dbUpd = DBI->connect( $dsn, $userid, $password, { RaiseError => 1 } )  or LifeLogException->throw("Execute failed [$DBI::errstri]");
+                traceDBExe($stm);
                 return;
             }
 
             if ( $view_all && $view_all == "1" ) {
-                $rec_limit = 0;
+                $rec_limit = &Settings::viewAllLimit;
             }
 
             if ( $view_mode == "1" ) {
@@ -987,92 +1055,83 @@ try {
                                         $sand .= "and ID_CAT!=$_ ";
                                 }
                         }
-                        else{        $sand = "and ID_CAT != $prm_xc"; }
+                        else{ $sand = "and ID_CAT != $prm_xc"; }
 
                     }
 
-                    $stmt = qq(SELECT PID, ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY from VW_LOG where PID <= $rs_cur and STICKY != 1 $sand;);
+                    $sqlVWL = qq(SELECT PID, ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY from VW_LOG where PID<=$rs_cur and STICKY!=1 $sand)." LIMIT ".&Settings::viewAllLimit.";";
                     return;
                 }
             }
 
             if ( $log && $date && $cat ) {
 
-                #check for double entry
+                                #
+                # After Insert renumeration check
                 #
-                my $stm = qq(SELECT DATE,LOG FROM LOG where DATE='$date' AND LOG='$log';);
-
-                my $st = $db->prepare($stm);
-                $st->execute();
+                my $dt    = DateTime::Format::SQLite->parse_datetime($date);
+                my $dtCur = DateTime->now();
+                $dtCur->set_time_zone(&Settings::timezone);
+                $dtCur = $dtCur - DateTime::Duration->new( days => 1 );
 
+                #check for double entry
+                #
+                $date = DateTime::Format::SQLite->parse_datetime($date);
+                $stm = qq(SELECT DATE,LOG FROM LOG where DATE='$date' AND LOG='$log';);
+                my $st = traceDBExe($stm);
                 if ($st->fetchrow_array() ) {
                     return;
                 }
-
-                $stm = qq(INSERT INTO LOG (ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY)
-                        VALUES($cat, '$date', '$log', $am, $af, $rtf, $sticky);
-                        );
-                print "\n###$stm\n" if $DEBUG;
-
-                $st = $db->prepare($stm);
-                $st->execute();
+                if ($dtCur > $dt){$sticky = 1; print $cgi->p("<b>Insert forced to be sticky, it is in the past!</b>");}
+                $stm = qq(INSERT INTO LOG (ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY) VALUES ($cat,$rtf,'$date','$log',$am,$af,$sticky););
+                $st = traceDBExe($stm);
                 if($sssCDB){
                     #Allow further new database creation, it is not an login infinite db creation attack.
                     $sss->param("cdb", 0);
                 }
-
                 if($rtf){ #Update 0 ground NOTES entry to the just inserted log.
 
-                   $st = $db->prepare('SELECT ID FROM VW_LOG LIMIT 1;');
-                   $st -> execute();
+                   $st = traceDBExe('SELECT ID FROM VW_LOG LIMIT 1;');
                    my @lid = $st->fetchrow_array();
-                   $st = $db->prepare("SELECT DOC FROM NOTES WHERE LID = '0';");
-                   $st -> execute();
+                   $st = traceDBExe('SELECT DOC FROM NOTES WHERE LID = 0;');
                    my @gzero = $st->fetchrow_array();
-
-
                    if(scalar @lid > 0){
-            #By Notes.LID contraint, there should NOT be an already existing log rowid entry just submitted in the Notes table!
+            #By Notes.LID constraint, there should NOT be an already existing log rowid entry just submitted in the Notes table!
             #What happened? We must check and delete, regardles. As data is renumerated and shuffled from perl in database. :(
-                      $st = $db->prepare("SELECT LID FROM NOTES WHERE LID = '$lid[0]';");
-                      $st->execute();
+                      $st = traceDBExe("SELECT LID FROM NOTES WHERE LID=".$lid[0].";");
                       if($st->fetchrow_array()){
-                          $st = $db->prepare("DELETE FROM NOTES WHERE LID = '$lid[0]';");
-                          $st->execute();
-                          print qq(<p>Warning deleted (possible old) NOTES.LID[$lid[0]] -> lid:$lid[0]</p>);
+                          $st = $db->do("DELETE FROM NOTES WHERE LID=".$lid[0].";");
+                          print qq(<p>Warning deleted (possible old) NOTES.LID[$lid[0]] -> lid:@lid</p>);
                       }
                       $st = $db->prepare("INSERT INTO NOTES(LID, DOC) VALUES (?, ?);");
-                     #
                       $st->execute($lid[0], $gzero[0]);
-
                        #Flatten ground zero
-                       $st = $db->prepare("UPDATE NOTES SET DOC='' WHERE LID = 0;");
-                       $st->execute();
+                      $st = $db->prepare("UPDATE NOTES SET DOC='' WHERE LID=0;");
+                      $st->execute();
                    }
-
-
-                }
-                #
-                # After Insert renumeration check
-                #
-                my $dt    = DateTime::Format::SQLite->parse_datetime($date);
-                my $dtCur = DateTime->now();
-                $dtCur->set_time_zone(&Settings::timezone);
-                $dtCur = $dtCur - DateTime::Duration->new( days => 1 );
-
-                if ( $dtCur > $dt ) {
-                    print $cgi->p('<b>Insert is in the past!</b>');
-                   Settings::renumerate($db);
                 }
+                Settings::renumerate($db) if ( $dtCur > $dt );
             }
 }
  catch {
 
- print "<font color=red><b>ERROR</b></font> -> " . $_;
- print qq(<html><body><pre>Reached2! -> $cnt, $cat, $date, $log, $am, $af, $rtf, $sticky </pre></body></html
-        );
-exit;
-  }
+my $err = $@;
+my $pwd = `pwd`;
+$pwd =~ s/\s*$//;
+
+my $dbg = qq(--DEBUG OUTPUT--\n
+    DSN:$dsn
+    stm:$stm
+    \@DB::args:@DB::args
+    \$DBI::err:$DBI::errstr
+    cnt:$cnt, cat:$cat, date:$date, log:$log, am:$am, af:$af, rtf:$rtf, sticky:$sticky);
+print $cgi->header,
+        "<hr><font color=red><b>SERVER ERROR</b></font> on ".DateTime->now.
+        "<hr><pre>$pwd/$0 -> &".caller." -> [<font color=red><b>$DBI::errstr</b></font>] $err\n$dbg</pre>",
+        $cgi->end_html;
+
+    exit;
+}
 }
 
     sub buildNavigationButtons {
@@ -1130,109 +1189,99 @@ exit;
     }
 
 sub authenticate {
-        try {
-
-            my $st = $db->prepare( "SELECT alias FROM AUTH WHERE alias='$userid' and passw='$password';");
-            $st->execute();
-            my @c = $st->fetchrow_array();
-            if (@c && $c[0] eq $userid ) { return; }
-
-            #Check if passw has been wiped for reset?
-            $st = $db->prepare("SELECT * FROM AUTH WHERE alias='$userid';");
-            $st->execute();
-            @c = $st->fetchrow_array();
-            if ( @c && $c[1] == "" ) {
-                #Wiped with -> UPDATE AUTH SET passw='' WHERE alias='$userid';
-                $st = $db->prepare("UPDATE AUTH SET passw='$password' WHERE alias='$userid';");
-                $st->execute();
-                return;
-            }
-
-            print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
-            print $cgi->start_html(
-                -title => "Personal Log Login",
-                -BGCOLOR => $BGCOL,
-                -script =>
-                  { -type => 'text/javascript', -src => 'wsrc/main.js' },
-                -style => { -type => 'text/css', -src => 'wsrc/main.css' },
-            );
-            if($DEBUG){
-                    print $cgi->center(
-                        $cgi->div("<b>Access Denied!</b> alias:$userid pass:$password SQL->SELECT * FROM AUTH WHERE alias='$userid' and passw='$password'; ")
-                    );
-            }
-            else{
-                    print $cgi->center(
-                        $cgi->div('<h2>Sorry Access Denied!</h2><font color=red><b>You supplied wrong credentials.</b></font>'),
-                        $cgi->div('<h3>[<a href="login_ctr.cgi">Login</a>]</h3>')
-                    );
-            }
-            print $cgi->end_html;
+    try {
 
-            $db->disconnect();
-            $sss->flush();
-            exit;
+        my $st = traceDBExe("SELECT alias FROM AUTH WHERE alias='$userid' and passw='$password';");
+        my @c = $st->fetchrow_array();
+        if (@c && $c[0] eq $userid ) { return; }
+
+        #Check if passw has been wiped for reset?
+        $st = traceDBExe("SELECT * FROM AUTH WHERE alias='$userid';");
+        @c = $st->fetchrow_array();
+        if ( @c && $c[1] == "" ) {
+            #Wiped with -> UPDATE AUTH SET passw='' WHERE alias='$userid';
+            $st = traceDBExe("UPDATE AUTH SET passw='$password' WHERE alias='$userid';");
+            return;
+        }
 
+        print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
+        print $cgi->start_html(
+            -title => "Personal Log Login",
+            -BGCOLOR => $BGCOL,
+            -script =>
+                { -type => 'text/javascript', -src => 'wsrc/main.js' },
+            -style => { -type => 'text/css', -src => 'wsrc/main.css' },
+        );
+        if($DEBUG){
+                print $cgi->center(
+                    $cgi->div("<b>Access Denied!</b> alias:$userid pass:$password SQL->SELECT * FROM AUTH WHERE alias='$userid' and passw='$password'; ")
+                );
         }
-        catch {
-            print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
-            print $cgi->p( "ERROR:" . $_ );
-            print $cgi->end_html;
-            exit;
+        else{
+                print $cgi->center(
+                    $cgi->div('<h2>Sorry Access Denied!</h2><font color=red><b>You supplied wrong credentials.</b></font>'),
+                    $cgi->div('<h3>[<a href="login_ctr.cgi">Login</a>]</h3>')
+                );
         }
-}
+        print $cgi->end_html;
 
-sub fetchAutocomplete {
-    try {
+        $db->disconnect();
+        $sss->flush();
+        exit;
 
-        while ( my @row = $st->fetchrow_array() ) {
-            my $log = $row[0];
+    }
+    catch {
+        print $cgi->header( -expires => "+0s", -charset => "UTF-8" );
+        print $cgi->p( "PAGE ERROR:" . $_ );
+        print $cgi->end_html;
+        exit;
+    }
+}
 
-            #Decode escaped \\n
-            $log =~ s/\\n/\n/gs;
-            $log =~ s/''/'/g;
+sub fetchAutocomplete {
+    my $st = traceDBExe('SELECT LOG from LOG' . $stmE );
+    while ( my @row = $st->fetchrow_array() ) {
+        my $log = $row[0];
 
-            #Replace link to empty string
-            my @words = split( /($re_a_tag)/si, $log );
-            foreach my $ch_i (@words) {
-                next if $ch_i =~ /$re_a_tag/;
-                next if index( $ch_i, "<img" ) > -1;
-                $ch_i =~ s/https//gsi;
-                $ch_i =~ s/($RE{URI}{HTTP})//gsi;
-            }
-            $log   = join( ' ', @words );
-            @words = split( ' ', $log );
-            foreach my $word (@words) {
-
-                #remove all non alphanumerics
-                $word =~ s/[^a-zA-Z]//gs;
-                if ( length($word) > 2 ) {
-                    $word = lc $word;
-
-                    #parse for already placed words, instead of using an hash.
-                    my $idx = index( $autowords, $word, 0 );
-                    if ( $idx > 0 ) {
-                        my $end = index( $autowords, '"', $idx );
-                        my $existing =
-                            substr( $autowords, $idx, $end - $idx );
-                        next if $word eq $existing;
-                    }
+        #Decode escaped \\n
+        $log =~ s/\\n/\n/gs;
+        $log =~ s/''/'/g;
 
-                    $autowords .= qq(,"$word");
-                    if ( $aw_cnt++ > &Settings::autoWordLimit ) {
-                        last;
-                    }
+        #Replace link to empty string
+        my @words = split( /($re_a_tag)/si, $log );
+        foreach my $ch_i (@words) {
+            next if $ch_i =~ /$re_a_tag/;
+            next if index( $ch_i, "<img" ) > -1;
+            $ch_i =~ s/https//gsi;
+            $ch_i =~ s/($RE{URI}{HTTP})//gsi;
+        }
+        $log   = join( ' ', @words );
+        @words = split( ' ', $log );
+        foreach my $word (@words) {
+
+            #remove all non alphanumerics
+            $word =~ s/[^a-zA-Z]//gs;
+            if ( length($word) > 2 ) {
+                $word = lc $word;
+                #parse for already placed words, instead of using an hash.
+                my $idx = index( $autowords, $word, 0 );
+                if ( $idx > 0 ) {
+                    my $end = index( $autowords, '"', $idx );
+                    my $existing =
+                        substr( $autowords, $idx, $end - $idx );
+                    next if $word eq $existing;
                 }
-            }
 
-            if ( $aw_cnt > &Settings::autoWordLimit ) {
-                last;
+                $autowords .= qq(,"$word");
+                if ( $aw_cnt++ > &Settings::autoWordLimit ) {
+                    last;
+                }
             }
         }
 
-    }
-    catch {
-        print "<font color=red><b>SERVER ERROR</b></font>:" . $_;
+        if ( $aw_cnt > &Settings::autoWordLimit ) {
+            last;
+        }
     }
 }
 
@@ -1338,22 +1387,22 @@ return qq(
     for your logs HTML layout.
     </p>
     <p>
-    <b>&#60;&#60;B&#60;<i>{Text To Bold}</i><b>&#62;</b>
+    <b>&#60;&#60;B&#60;<i>{Text To Bold}</i><b>&#62;&#62;</b>
     </p>
     <p>
-    <b>&#60;&#60;I&#60;<i>{Text To Italic}</i><b>&#62;</b>
+    <b>&#60;&#60;I&#60;<i>{Text To Italic}</i><b>&#62;&#62;</b>
     </p>
     <p>
-    <b>&#60;&#60;TITLE&#60;<i>{Title Text}</i><b>&#62;</b>
+    <b>&#60;&#60;TITLE&#60;<i>{Title Text}</i><b>&#62;&#62;</b>
     </p>
     <p>
     <b>&#60;&#60;LIST&#60;<i>{List of items delimited by new line to terminate item or with '~' otherwise.}</i><b>&#62;</b>
     </p>
     <p>
-    <b>&#60;&#60;IMG&#60;<i>{url to image}</i><b>&#62;</b>
+    <b>&#60;&#60;IMG&#60;<i>{url to image}</i><b>&#62;&#62;</b>
     </p>
     <p>
-        <b>&#60;&#60;FRM&#60;<i>{file name}_frm.png}</i><b>&#62;</b><br><br>
+        <b>&#60;&#60;FRM&#60;<i>{file name}_frm.png}</i><b>&#62;&#62;</b><br><br>
         *_frm.png images file pairs are located in the ./images folder of the cgi-bin directory.<br>
         These are manually resized by the user. Next to the original.
         Otherwise considered as stand alone icons. *_frm.png Image resized to ->  width="210" height="120"
@@ -1365,12 +1414,12 @@ return qq(
 
           For log entry, place:
 
-         &#60;&#60;FRM&#62;my_cat_simon_frm.png&#62; &#60;&#60;TITLE&#60;Simon The Cat&#62;
+         &#60;&#60;FRM&#62;my_cat_simon_frm.png&#62; &#60;&#60;TITLE&#60;Simon The Cat&#62;&#62;
          This is my pet, can you hold him for a week while I am on holiday?
             </pre>
                                        </p>
                                        <p>
-                                       <b>&#60;&#60;LNK&#60;<i>{url to image}</i><b>&#62;</b><br><br>
+                                       <b>&#60;&#60;LNK&#60;<i>{url to image}</i><b>&#62;&#62;</b><br><br>
                                        Explicitly tag an URL in the log entry.
                                        Required if using in log IMG or FRM tags.
                                        Otherwise link appears as plain text.
index 914587f0174520e08cca5a035f6fd07bab3ad5a9..8af0a8a9be6a0ca305b581cf52292ca1cc272ef6 100755 (executable)
@@ -5,8 +5,6 @@
 use strict;
 use warnings;
 #no warnings 'uninitialized';
-
-use Try::Tiny;
 use Switch;
 
 use CGI;
@@ -16,27 +14,21 @@ use DateTime;
 use DateTime::Format::SQLite;
 use Number::Bytes::Human qw(format_bytes);
 use IPC::Run qw( run );
+use Syntax::Keyword::Try;
 
-
-#SETTINGS HERE!
-my $REC_LIMIT = 25;
-my $TIME_ZONE    = 'Australia/Sydney';
-my $LOG_PATH     = '../../dbLifeLog/';
-my $RELEASE_VER  = "";
-my $THEME        = 0;
-my $TH_CSS       = 'main.css';
-my $DEBUG = 0;
-#END OF SETTINGS
+use lib "system/modules";
+use lib $ENV{'PWD'}.'/htdocs/cgi-bin/system/modules';
+require Settings;
 
 my $cgi = CGI->new;
-my $session = new CGI::Session("driver:File",$cgi, {Directory=>$LOG_PATH});
+my $session = new CGI::Session("driver:File",$cgi, {Directory=>&Settings::logPath});
 my $sid=$session->id();
 my $dbname  =$session->param('database');
 my $userid  =$session->param('alias');
 my $password=$session->param('passw');
 
 if(!$userid||!$dbname){
-    if ($DEBUG){
+    if (&Settings::debug){
         $userid ="admin";
         $dbname = "data_admin_log.db";
         $password = "admin";
@@ -46,45 +38,32 @@ if(!$userid||!$dbname){
     exit;
     }
 }
+my $db = "";
 
-my $database = '../../dbLifeLog/' . $dbname;
-my $dsn      = "DBI:SQLite:dbname=$database";
-my $db       = DBI->connect( $dsn, $userid, $password, { RaiseError => 1 } ) or die "<p>Error->" & $DBI::errstri & "</p>";
-my @stat = stat $database;
+try{
 
-##################
-&getConfiguration;
-##################
+my $database = &Settings::logPath . $dbname;
+my @stat = stat $database;
+my $dsn  = "DBI:SQLite:dbname=$database";
+$db      = DBI->connect( $dsn, $userid, $password, { RaiseError => 1 } );
 
+Settings::getConfiguration($db);
+Settings::getTheme();
 
 my $today = DateTime->now;
-$today->set_time_zone( $TIME_ZONE );
-
-
-my $BGCOL = '#c8fff8';
-if ( $THEME eq 'Sun' ) {
-    $BGCOL = '#D4AF37';
-    $TH_CSS = "main_sun.css";
-}elsif ($THEME eq 'Moon'){
-    $TH_CSS = "main_moon.css";
-    $BGCOL = '#000000';
-
-}elsif ($THEME eq 'Earth'){
-    $TH_CSS = "main_earth.css";
-    $BGCOL = 'green';
-}
+$today->set_time_zone(&Settings::timezone);
 
 $ENV{'HOME'} = "~/";
 
 
 print $cgi->header(-expires=>"+6os", -charset=>"UTF-8");
-print $cgi->start_html(-title => "Log Data Stats", -BGCOLOR=>"$BGCOL",
+print $cgi->start_html(-title => "Log Data Stats", -BGCOLOR=>&Settings::bgcol,
                        -script=> [{-type => 'text/javascript', -src => 'wsrc/main.js'},
                                   {-type => 'text/javascript', -src => 'wsrc/jquery.js' },
-                                  { -type => 'text/javascript', -src => 'wsrc/jquery-ui.js' }],
-                       -style => [{-type => 'text/css', -src => "wsrc/$TH_CSS"},
-                                  { -type => 'text/css', -src => 'wsrc/jquery-ui.css' },
-                                  { -type => 'text/css', -src => 'wsrc/jquery-ui.theme.css' }],
+                                  {-type => 'text/javascript', -src => 'wsrc/jquery-ui.js' }],
+                       -style => [{-type => 'text/css', -src => "wsrc/".&Settings::css},
+                                  {-type => 'text/css', -src => 'wsrc/jquery-ui.css' },
+                                  {-type => 'text/css', -src => 'wsrc/jquery-ui.theme.css' }],
 
                        -onload  => "onBodyLoadGeneric()"
                 );
@@ -120,13 +99,13 @@ run \@cmd, '>&', \$HS; #instead of -> system("inxi",'-b', '-c0');
 my $hardware_status = $HS;#`inxi -b -c0; uptime -p`;
 my $syslog = "<b>".substr $HS, index($HS, 'Host:'), index($HS, 'Console:');
    $syslog .= "</b>\n".substr $HS, rindex($HS, 'Info:');
-   $HS = "<pre>".`df -h -l -x tmpfs`."</pre>";
+   $HS = "<pre>\n".`df -h -l -x tmpfs`."</pre>";
    $syslog .= $HS;
 $hardware_status =~ s/\n/<br\/>/g;
 $hardware_status =~ s/Memory:/<b>Memory:/g;
 $hardware_status =~ s/Init:/<\/b>Initial:/g;
 $hardware_status =~ s/up\s/<b>Server is up: /g;
-$hardware_status .= "</b>$HS";
+$hardware_status .= '</b>';
 
 my $prc = 'ps -eo size,pid,user,command --sort -size | awk \'{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }\'';
 my  $processes = `$prc | sort -u -r -`;
@@ -140,7 +119,7 @@ my $year =$today->year();
 my $IPPublic  = `curl -s https://www.ifconfig.me`;
 my $IPPrivate = `hostname -I`; $IPPrivate =~ s/\s/<br>/g;
 
-$tbl .=qq(<tr class="r1"><td>LifeLog App. Version:</td><td>$RELEASE_VER</td></tr>
+$tbl .=qq(<tr class="r1"><td>LifeLog App. Version:</td><td>).&Settings::release.qq(</td></tr>
              <tr class="r0"><td>Number of Records:</td><td>$log_rc</td></tr>
           <tr class="r1"><td>No. of Records This Year:</td><td>$log_this_year_rc</td></tr>
           <tr class="r0"><td>No. of RTF Documents:</td><td>$notes_rc</td></tr>
@@ -166,21 +145,33 @@ print qq(
     <span style="text-align:left; margin:1px; padding-right:15px; float:none;"><h2>Server Info</h2><hr><br>
     $hardware_status</span><hr>
 </div>
+$HS
 <div class="tbl" style="text-align:left; border: 0px; padding:5px;">
     <b>Server Side Processes</b><hr>
 </div>
 <pre>$processes</pre>);
 print $cgi->end_html;
-my $date = DateTime::Format::SQLite->parse_datetime($today);
-dbExecute(qq(  INSERT INTO LOG (ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF, STICKY) VALUES(6, '$date', '$syslog', 0, 0, 0, 0); ));
+
+&Settings::toLog($db,$syslog);
 $db->disconnect();
+
+}
+ catch {
+            my $err = $@;
+            my $pwd = `pwd`;
+            $pwd =~ s/\s*$//;
+            print $cgi->header,
+            "<font color=red><b>SERVER ERROR</b></font> on ".DateTime->now.
+            "<pre>".$pwd."/$0 -> &".caller." -> [$err]","\n</pre>",
+            $cgi->end_html;
+ };
+
+
 exit;
 
-sub selectSQL{
-    my ($sth,$ret) = dbExecute( @_ );
-    my @row = $sth->fetchrow_array();
-    $sth->finish;
-    $ret = $row[0];
+sub selectSQL {
+    my @row = Settings::selectRecords($db, shift)->fetchrow_array();
+    my $ret = $row[0];
     $ret = 0 if !$ret;
 return $ret;
 }
@@ -201,27 +192,4 @@ sub camm {
 return $amm;
 }
 
-sub getConfiguration {
-    try{
-        my $st = dbExecute('SELECT ID, NAME, VALUE FROM CONFIG;');
-        while (my @r=$st->fetchrow_array()){
-            switch ($r[1]) {
-                case "RELEASE_VER" { $RELEASE_VER  = $r[2] }
-                case "THEME"       {$THEME= $r[2]}
-            }
-        }
-    }
-    catch{
-        print "<font color=red><b>SERVER ERROR</b></font>:".$_;
-    }
-
-}
-
-sub dbExecute{
-    my $ret    = $db->prepare(shift);
-       $ret->execute() or die "<p>Error->"& $DBI::errstri &"</p>";
-    return $ret;
-}
-
-
-### CGI END
\ No newline at end of file
+1.
\ No newline at end of file
index 0818d0c08d5c700ae2ecd8eb599a20d0617a56df..455e571454cb1cba3ec76d5d2878d1979727ef76 100644 (file)
@@ -8,12 +8,16 @@ package Settings;
 use strict;
 use warnings;
 use Switch;
+use Exception::Class ('SettingsException');
+use Syntax::Keyword::Try;
 
 use DBI;
 
+#This is the default developer release key, replace on istallation. As it is not secure.
+use constant CIPHER_KEY => '95d7a85ba891da';
+
 #DEFAULT SETTINGS HERE!
-our $RELEASE_VER  = '1.7';
-our $REC_LIMIT    = 25;
+our $RELEASE_VER  = '1.8';
 our $TIME_ZONE    = 'Australia/Sydney';
 our $LANGUAGE     = 'English';
 our $PRC_WIDTH    = '60';
@@ -22,81 +26,166 @@ our $SESSN_EXPR   = '+30m';
 our $DATE_UNI     = '0';
 our $AUTHORITY    = '';
 our $IMG_W_H      = '210x120';
+our $REC_LIMIT    = 25;
 our $AUTO_WRD_LMT = 1000;
+our $VIEW_ALL_LMT = 1000;
 our $FRAME_SIZE   = 0;
 our $RTF_SIZE     = 0;
 our $THEME        = 'Standard';
+our $TRACK_LOGINS = 1;
 our $KEEP_EXCS    = 0;
 
+#Annons here, variables that could be overiden in  code or database, per need.
+my %anons = ();
+
 ### Page specific settings Here
 our $TH_CSS        = 'main.css';
 our $BGCOL         = '#c8fff8';
 #Set to 1 to get debug help. Switch off with 0.
-our $DEBUG         = 0;
+our $DEBUG         = 1;
 #END OF SETTINGS
 
-
 ### Private Settings sofar (id -> name : def.value):
 #200 -> '^REL_RENUM' : this.$RELEASE_VER (Used in login_ctr.cgi)
 #201 -> '^EXCLUDES'  : 0 (Used in main.cgi)
 
+sub anons {return sort keys %anons}
+#Check call with defined(Settings::anon('my_anon'))
+sub anon {my $n=shift; return $anons{$n}}
 
-
-
-sub new {
-    return bless {}, shift;
-}
-sub release        {return $RELEASE_VER;}
-sub logPath        {return $LOG_PATH;}
-sub theme          {return $THEME;}
-sub language       {return $LANGUAGE;}
-sub timezone       {return $TIME_ZONE;}
-sub sessionExprs   {return $SESSN_EXPR;}
-sub imgWidthHeight {return $IMG_W_H;}
-sub pagePrcWidth   {return $PRC_WIDTH;}
-sub recordLimit    {return $REC_LIMIT;}
-sub frameSize      {return $FRAME_SIZE;}
+sub release        {return $RELEASE_VER}
+sub logPath        {return $LOG_PATH}
+sub theme          {return $THEME}
+sub language       {return $LANGUAGE}
+sub timezone       {return $TIME_ZONE}
+sub sessionExprs   {return $SESSN_EXPR}
+sub imgWidthHeight {return $IMG_W_H}
+sub pagePrcWidth   {return $PRC_WIDTH}
+sub frameSize      {return $FRAME_SIZE}
 sub universalDate  {return $DATE_UNI;}
-sub autoWordLimit  {return $AUTO_WRD_LMT;}
-sub windowRTFSize  {return $RTF_SIZE;}
-sub keepExcludes   {return $KEEP_EXCS;}
-sub bgcol          {return $BGCOL;}
-sub css            {return $TH_CSS;}
+sub recordLimit    {return $REC_LIMIT}
+sub autoWordLimit  {return $AUTO_WRD_LMT}
+sub viewAllLimit   {return $VIEW_ALL_LMT}
+sub trackLogins    {return $TRACK_LOGINS}
+sub windowRTFSize  {return $RTF_SIZE}
+sub keepExcludes   {return $KEEP_EXCS}
+sub bgcol          {return $BGCOL}
+sub css            {return $TH_CSS}
 sub debug          {my $ret=shift; if($ret){$DEBUG = $ret;}; return $DEBUG;}
 
-
+sub createCONFIGStmt {
+return qq(
+    CREATE TABLE CONFIG(
+        ID TINY             PRIMARY KEY NOT NULL,
+        NAME VCHAR(16)      UNIQUE,
+        VALUE VCHAR(28),
+        DESCRIPTION VCHAR(128)
+    );
+    CREATE INDEX idx_config_name ON CONFIG (NAME);
+)}
+sub createCATStmt {
+return qq(
+    CREATE TABLE CAT(
+        ID TINY             PRIMARY KEY NOT NULL,
+        NAME                VCHAR(16),
+        DESCRIPTION         VCHAR(64)
+    );
+    CREATE INDEX idx_cat_name ON CAT (NAME);
+)}
+sub createLOGStmt {
+return qq(
+    CREATE TABLE LOG (
+        ID_CAT TINY        NOT NULL,
+        ID_RTF INTEGER     DEFAULT 0,
+        DATE   DATETIME    NOT NULL,
+        LOG    VCHAR (128) NOT NULL,
+        AMOUNT INTEGER,
+        AFLAG TINY         DEFAULT 0,
+        STICKY BOOL        DEFAULT 0
+    );
+)}
+sub createVW_LOGStmt {
+return qq(
+CREATE VIEW VW_LOG AS
+    SELECT rowid as ID,*, (select count(rowid) from LOG as recount where a.rowid >= recount.rowid) as PID
+        FROM LOG as a ORDER BY Date(DATE) DESC;'
+)}
+sub createAUTHStmt {
+return qq(
+    CREATE TABLE AUTH(
+        ALIAS varchar(20)   PRIMARY KEY,
+        PASSW TEXT,
+        EMAIL               varchar(44),
+        ACTION TINY
+    ) WITHOUT ROWID;
+    CREATE INDEX idx_auth_name_passw ON AUTH (ALIAS, PASSW);
+)}
+sub createNOTEStmt {
+    return qq(CREATE TABLE NOTES (LID INTEGER PRIMARY KEY NOT NULL, DOC TEXT);)
+}
+sub createLOGCATSREFStmt {
+return qq(
+    CREATE TABLE LOGCATSREF (
+        LID INTEGER NOT NULL,
+        CID TINY NOT NULL,
+    FOREIGN KEY (LID) REFERENCES LOG(ID),
+    FOREIGN KEY(CID) REFERENCES CAT(ID)
+    );
+)}
 
 sub getConfiguration {
-    my $db = shift;
+    my ($db, $hsh) = @_;
     try {
         my $st = $db->prepare("SELECT ID, NAME, VALUE FROM CONFIG;");
-        $st->execute();
-
-        while ( my @r = $st->fetchrow_array() ) {
-
-            switch ( $r[1] ) {
-                case "RELEASE_VER"  { $RELEASE_VER  = $r[2] }
-                case "REC_LIMIT"    { $REC_LIMIT    = $r[2] }
-                case "TIME_ZONE"    { $TIME_ZONE    = $r[2] }
-                case "PRC_WIDTH"    { $PRC_WIDTH    = $r[2] }
-                case "SESSN_EXPR"   { $SESSN_EXPR   = $r[2] }
-                case "DATE_UNI"     { $DATE_UNI     = $r[2] }
-                case "LANGUAGE"     { $LANGUAGE     = $r[2] }
-                case "IMG_W_H"      { $IMG_W_H      = $r[2] }
-                case "AUTO_WRD_LMT" { $AUTO_WRD_LMT = $r[2] }
-                case "FRAME_SIZE"   { $FRAME_SIZE   = $r[2] }
-                case "RTF_SIZE"     { $RTF_SIZE     = $r[2] }
-                case "THEME"        { $THEME        = $r[2] }
-                case "DEBUG"        { $DEBUG        = $r[2] }
-                case "KEEP_EXCS"    { $KEEP_EXCS    = $r[2] }
+           $st->execute();
+        while ( my @r = $st->fetchrow_array() ){
+                switch ( $r[1] ) {
+                case "RELEASE_VER"  { $RELEASE_VER  = $r[2];}
+                case "TIME_ZONE"    { $TIME_ZONE    = $r[2];}
+                case "PRC_WIDTH"    { $PRC_WIDTH    = $r[2];}
+                case "SESSN_EXPR"   { $SESSN_EXPR   = $r[2];}
+                case "DATE_UNI"     { $DATE_UNI     = $r[2];}
+                case "LANGUAGE"     { $LANGUAGE     = $r[2];}
+                case "LOG_PATH"     {} #ommited and code static can't change for now.
+                case "IMG_W_H"      { $IMG_W_H      = $r[2];}
+                case "REC_LIMIT"    { $REC_LIMIT    = $r[2];}
+                case "AUTO_WRD_LMT" { $AUTO_WRD_LMT = $r[2];}
+                case "VIEW_ALL_LMT" { $VIEW_ALL_LMT = $r[2];}
+                case "FRAME_SIZE"   { $FRAME_SIZE   = $r[2];}
+                case "RTF_SIZE"     { $RTF_SIZE     = $r[2];}
+                case "THEME"        { $THEME        = $r[2];}
+                case "DEBUG"        { $DEBUG        = $r[2];}
+                case "KEEP_EXCS"    { $KEEP_EXCS    = $r[2];}
+                case "TRACK_LOGINS" { $TRACK_LOGINS = $r[2];}
+                else                { $anons{$r[1]} = $r[2];}
+                }
+        }
+        #Anons are murky grounds. -- @bud
+        if($hsh){
+            my $stIns = $db->prepare("INSERT INTO CONFIG (ID, NAME, VALUE, DESCRIPTION) VALUES(?,?,?,?)");
+            foreach my $key (keys %{$hsh}){
+                if(index($key,'$')!=0){#per spec. anons are not prefixed with an '$' as signifier.
+                    my $val = %{$hsh}{$key};
+                    my $existing = $anons{$key};
+                    #exists? Overwrite for $self config but not in DB! (dynamic code base set anon)
+                    $anons{$key} = $val;
+                    if(not defined $existing){
+                        #Make it now config global. Note another source latter calling this subroutine
+                        #can overwrite this, but not in the database. Where it is now set by the following.
+                        #Find free ID.
+                        my @res = selectRecords($db,"SELECT MAX(ID) FROM CONFIG;")->fetchrow_array();
+                        #ID's under 300 are reserved, for constants.
+                        my $id = $res[0]+1;
+                        while($id<300){ $id += ($id*1.61803398875); }#Golden ratio step it to best next available.
+                        $stIns->execute(int($id), $key, $val, "Anonymous application setting.");
+                    }
+                }
             }
-
         }
-        return &new;
     }
     catch {
-        print "<font color=red><b>SERVER ERROR</b></font>:" . $_;
-    }
+        SettingsException->throw(error=>$@, show_trace=>$DEBUG);
+    };
 }
 
 
@@ -105,7 +194,7 @@ sub getTheme {
         switch ($THEME){
             case "Sun"   { $BGCOL = '#D4AF37'; $TH_CSS = "main_sun.css"; }
             case "Moon"  { $BGCOL = '#000000'; $TH_CSS = "main_moon.css"; }
-            case "Earth" { $BGCOL = '#26be54'; $TH_CSS = "main_earth.css"; }
+            case "Earth" { $BGCOL = '#26ac0c'; $TH_CSS = "main_earth.css";} # Used to be $BGCOL = '#26be54';
             else{
                 # Standard;
                 $BGCOL    = '#c8fff8';
@@ -116,52 +205,75 @@ sub getTheme {
 }
 
 
+
+#From v.1.8 Changed
 sub renumerate {
     my $db = shift;
     #Renumerate Log! Copy into temp. table.
     my $sql;
-    my $dbs = dbExecute($db, 'CREATE TABLE life_log_temp_table AS SELECT * FROM LOG;');
-       $dbs = dbExecute($db, 'SELECT rowid, DATE FROM LOG WHERE RTF == 1 ORDER BY DATE;');
-    #update  notes with new log id
-    while(my @row = $dbs->fetchrow_array()) {
+    selectRecords($db, 'CREATE TABLE life_log_temp_table AS SELECT * FROM LOG;');
+    #update  notes table with new log id only for reference sake.
+    my $st = selectRecords($db, 'SELECT rowid, DATE FROM LOG WHERE ID_RTF > 0 ORDER BY DATE;');
+    while(my @row =$st->fetchrow_array()) {
         my $sql_date = $row[1];
         #$sql_date =~ s/T/ /;
         $sql_date = DateTime::Format::SQLite->parse_datetime($sql_date);
-        $sql = "SELECT rowid, DATE FROM life_log_temp_table WHERE RTF = 1 AND DATE = '".$sql_date."';";
-        $dbs = dbExecute($db, $sql);
-        my @new  = $dbs->fetchrow_array();
+        $sql = "SELECT rowid, DATE FROM life_log_temp_table WHERE ID_RTF > 0 AND DATE = '".$sql_date."';";
+        my @new  = selectRecords($db, $sql)->fetchrow_array();
         if(scalar @new > 0){
-            $db->do("UPDATE NOTES SET LID =". $new[0]." WHERE LID==".$row[0].";");
+             try{#can fail here, for various reasons.
+                $sql="UPDATE NOTES SET LID =". $new[0]." WHERE LID==".$row[0].";";
+                $db->do($sql);
+             }
+             catch{
+                 SettingsException->throw(error=>"Database error encountered. sql->$sql", show_trace=>$DEBUG);
+             };
         }
     }
 
-    # Delete Orphaned Notes entries.
-    $dbs = dbExecute($db, "SELECT LID, LOG.rowid from NOTES LEFT JOIN LOG ON
+    # Delete any possible orphaned Notes records.
+    $st = selectRecords($db, "SELECT LID, LOG.rowid from NOTES LEFT JOIN LOG ON
                                     NOTES.LID = LOG.rowid WHERE LOG.rowid is NULL;");
-    while(my @row = $dbs->fetchrow_array()) {
-        $db->do("DELETE FROM NOTES WHERE LID=$row[0];");
+    while($st->fetchrow_array()) {
+        $db->do("DELETE FROM NOTES WHERE LID=".$_[0].";")
     }
-    $dbs = dbExecute($db, 'DROP TABLE LOG;');
-    $dbs = dbExecute($db, qq(CREATE TABLE LOG (
-                            ID_CAT TINY        NOT NULL,
-                            DATE   DATETIME    NOT NULL,
-                            LOG    VCHAR (128) NOT NULL,
-                            AMOUNT INTEGER,
-                            AFLAG TINY DEFAULT 0,
-                            RTF BOOL DEFAULT 0,
-                            STICKY BOOL DEFAULT 0
-                            );));
-    $dbs = dbExecute($db, 'INSERT INTO LOG (ID_CAT,DATE,LOG,AMOUNT,AFLAG, RTF)
-                                    SELECT ID_CAT, DATE, LOG, AMOUNT, AFLAG, RTF
-                                    FROM life_log_temp_table ORDER by DATE;');
-    $dbs = dbExecute($db, 'DROP TABLE life_log_temp_table;');
+    $db->do('DROP TABLE LOG;');
+    $db->do(&createLOGStmt);
+    $db->do('INSERT INTO LOG (ID_CAT, ID_RTF, DATE, LOG, AMOUNT,AFLAG,STICKY)
+                       SELECT ID_CAT, ID_RTF, DATE, LOG, AMOUNT, AFLAG, STICKY FROM life_log_temp_table ORDER by DATE;');
+    $db->do('DROP TABLE life_log_temp_table;');
 }
 
-sub dbExecute {
-    my ($db,$sql) = @_;
-    my $ret    = $db->prepare($sql);
-       $ret->execute() or die "<p>ERROR with->$sql</p>";
-    return $ret;
+sub selectRecords {
+    my ($db, $sql) = @_;
+    if(scalar(@_) < 2){
+                SettingsException->throw("ERROR Argument number is wrong->db is:$db\n", show_trace=>$DEBUG);
+    }
+    try{
+                my $pst        = $db->prepare($sql) or SettingsException->throw("<p>ERROR with->$sql</p>", show_trace=>$DEBUG);
+                $pst->execute();
+                return 0 if(!$pst);
+                return $pst;
+    }catch{
+                SettingsException->throw(error=>"Database error encountered. Settings::selectRecords[$sql]", show_trace=>$DEBUG);
+    };
+}
+
+sub getTableColumnNames {
+        my ($db, $table_name) = @_;
+        if(scalar(@_) < 2){
+                SettingsException->throw("ERROR Argument number is wrong->db is:$db\n", show_trace=>$DEBUG);
+        }
+        try{
+                my $pst = selectRecords($db, "SELECT name FROM PRAGMA_table_info('$table_name');");
+                my @ret = ();
+                while(my @r = $pst->fetchrow_array()){
+                    push @ret, $r[0];
+                }
+                return \@ret;
+        }catch{
+                SettingsException->throw(error=>"Database error encountered.", show_trace=>$DEBUG);
+        }
 }
 
 sub printDebugHTML {
@@ -170,15 +282,41 @@ sub printDebugHTML {
 }
 
 sub toLog {
-    my ($db,$stamp,$log) = @_;
-    # try {
-        #Apostrophe in the log value is doubled to avoid SQL errors.
-        $log =~ s/'/''/g;
-        $db->do("INSERT INTO LOG (ID_CAT, DATE, LOG) VALUES(6,'$stamp', \"$log\");");
-    # }
-    # catch {
-    #     print "<font color=red><b>SERVER ERROR toLog(6,$stamp,$log)</b></font>:" . $_;
-    # }
+    my ($db,$log,$cat) = @_;
+    my $stamp = getCurrentSQLTimeStamp();
+        if(!$cat){
+            my @arr = selectRecords($db,"SELECT ID FROM CAT WHERE NAME LIKE 'System Log' or NAME LIKE 'System';")->fetchrow_array();
+            if(@arr){
+                $cat = $arr[0];
+            }
+            else{
+                $cat = 6;
+            }
+        }
+       $log =~ s/'/''/g;
+       $db->do("INSERT INTO LOG (ID_CAT, DATE, LOG) VALUES($cat,'$stamp', \"$log\");");
+}
+
+sub countRecordsIn {
+    my ($db,$name) = @_;
+     if(scalar(@_) < 2){
+        SettingsException->throw("ERROR Argument number is wrong.name:$name\n", show_trace=>$DEBUG);
+    }
+    my $ret = selectRecords($db, "SELECT count(ID) FROM $name;");
+    if($ret){
+       $ret ->fetchrow_array();
+       $ret = 0 if not $ret;
+    }
+    return $ret;
+}
+
+sub getCurrentSQLTimeStamp {
+
+     my $dt = DateTime->from_epoch(epoch => time(), time_zone=> $TIME_ZONE);
+     # 20200225  Found that SQLite->format_datetime, changes internally to UTC timezone, which is wrong.
+     # Strange that this format_datetime will work from time to time, during day and some dates. (A Pitfall)
+    #return DateTime::Format::SQLite->format_datetime($dt);
+    return join ' ', $dt->ymd('-'), $dt->hms(':');
 }
 
 sub removeOldSessions {
@@ -195,31 +333,62 @@ sub removeOldSessions {
 }
 
 
-#TODO move this subroutine to settings.
+
 sub obtainProperty {
     my($db, $name) = @_;
-    die "Invalid use of subroutine obtainProperty($db, $name)" if(!$db || !$name);
-    my $dbs = Settings::dbExecute($db, "SELECT ID, VALUE FROM CONFIG WHERE NAME IS '$name';");
+    SettingsException->throw("Invalid use of subroutine obtainProperty($db, $name)", show_trace=>$DEBUG) if(!$db || !$name);
+    my $dbs = selectRecords($db, "SELECT ID, VALUE FROM CONFIG WHERE NAME IS '$name';");
     my @row = $dbs->fetchrow_array();
     if(scalar @row > 0){
        return $row[1];
-     }
-     else{
+    }
+    else{
        return 0;
-     }
+    }
 }
-#TODO move this subroutine to settings.
+
 sub configProperty {
     my($db, $id, $name, $value) = @_;
-    die "Invalid use of subroutine configProperty($db,$name,$value)" if(!$db || !$name|| !$value);
+    $id = '0' if not $id;
+    if($db eq undef || $value eq undef){
+        SettingsException->throw(
+            error => "ERROR Invalid number of arguments in call -> Settings::configProperty('$db',$id,'$name','$value')\n",  show_trace=>$DEBUG
+            );
+    };
+    if($name eq undef && $id){
+
+        my $sql = "UPDATE CONFIG SET VALUE='".$value."' WHERE ID=".$id.";";
+        try{
+            $db->do($sql);
+        }
+        catch{
 
-    my $dbs = Settings::dbExecute($db, "SELECT ID, NAME FROM CONFIG WHERE NAME IS '$name';");
-    if($dbs->fetchrow_array()){
-       Settings::dbExecute($db, "UPDATE CONFIG SET VALUE = '$value' WHERE NAME IS '$name';");
+            SettingsException->throw(
+                error => "ERROR with $sql -> Settings::configProperty('$db',$id,'$name','$value')\n",
+                show_trace=>$DEBUG
+                );
+        }
     }
     else{
-       Settings::dbExecute($db,"INSERT INTO CONFIG (ID, NAME, VALUE) VALUES ($id, '$name', '$value');");
+        my $dbs = selectRecords($db, "SELECT ID, NAME FROM CONFIG WHERE NAME IS '$name';");
+        if($dbs->fetchrow_array()){
+            $db->do("UPDATE CONFIG SET VALUE = '$value' WHERE NAME IS '$name';");
+        }
+        else{
+            my $sql = "INSERT INTO CONFIG (ID, NAME, VALUE) VALUES ($id, '$name', '$value');";
+            try{
+                $db->do($sql);
+            }
+            catch{
+
+                SettingsException->throw(
+                    error => "ERROR $@ with $sql -> Settings::configProperty('$db',$id,'$name','$value')\n",
+                    show_trace=>$DEBUG
+                    );
+            }
+        }
     }
 }
 
+
 1;
\ No newline at end of file
diff --git a/htdocs/cgi-bin/wsrc/jquery.sweet-dropdown.css b/htdocs/cgi-bin/wsrc/jquery.sweet-dropdown.css
new file mode 100644 (file)
index 0000000..17b9374
--- /dev/null
@@ -0,0 +1,413 @@
+.dropdown-menu {
+ position:absolute;
+ z-index:9999999;
+ display:none;
+ opacity:0;
+ top:0;
+ left:0;
+ -webkit-transition:opacity 0.2s, -webkit-transform 0.2s;
+ transition:opacity 0.2s, transform 0.2s;
+ -webkit-transform:translateY(-20px) scale(0.93);
+ transform:translateY(-20px) scale(0.93);
+ border-radius:3px
+}
+.dropdown-menu.dropdown-opened {
+ opacity:1;
+ -webkit-transform:none !important;
+ transform:none !important
+}
+.dropdown-menu.fixed {
+ position:fixed
+}
+.dropdown-menu.dropdown-anchor-left-top,
+.dropdown-menu.dropdown-anchor-left-center,
+.dropdown-menu.dropdown-anchor-left-bottom {
+ -webkit-transform:translateX(-20px) scale(0.93);
+ transform:translateX(-20px) scale(0.93)
+}
+.dropdown-menu.dropdown-anchor-right-top,
+.dropdown-menu.dropdown-anchor-right-center,
+.dropdown-menu.dropdown-anchor-right-bottom {
+ -webkit-transform:translateX(20px) scale(0.93);
+ transform:translateX(20px) scale(0.93)
+}
+.dropdown-menu .dropdown-anchor {
+ border:7px solid #2fa6ca
+}
+.dropdown-menu .dropdown-anchor,
+.dropdown-menu .dropdown-anchor::after {
+ position:absolute;
+ content:'';
+ display:inline-block
+}
+.dropdown-menu .dropdown-anchor::after {
+ before-border:6px solid #fff
+}
+.dropdown-menu.dark .dropdown-anchor,
+.dropdown-menu.dark .dropdown-anchor::after {
+ border-color:#32363F
+}
+.dropdown-menu.grey-anchor .dropdown-anchor,
+.dropdown-menu.grey-anchor .dropdown-anchor::after {
+ border-color:#f6f6f6
+}
+.dropdown-menu.accent-anchor .dropdown-anchor,
+.dropdown-menu.accent-anchor .dropdown-anchor::after {
+ border-color:#F57C00
+}
+.dropdown-menu.dropdown-anchor-top-left:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-top-center:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-top-right:not(.dropdown-overlay-trigger) {
+ margin-top:10px
+}
+.dropdown-menu.dropdown-anchor-top-left .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-right .dropdown-anchor {
+ border-top-color:transparent;
+ border-right-color:transparent;
+ border-left-color:transparent;
+ top:-14px
+}
+.dropdown-menu.dropdown-anchor-top-left .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-top-center .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-top-right .dropdown-anchor::after {
+ border-top-color:transparent;
+ border-right-color:transparent;
+ border-left-color:transparent;
+ margin-top:-5px;
+ margin-left:-6px
+}
+.dropdown-menu.dropdown-anchor-top-left.dropdown-anchor-top-left .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-center.dropdown-anchor-top-left .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-right.dropdown-anchor-top-left .dropdown-anchor {
+ left:15px
+}
+.dropdown-menu.dropdown-anchor-top-left.dropdown-anchor-top-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-center.dropdown-anchor-top-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-right.dropdown-anchor-top-center .dropdown-anchor {
+ left:calc(50% - 7px)
+}
+.dropdown-menu.dropdown-anchor-top-left.dropdown-anchor-top-right .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-center.dropdown-anchor-top-right .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-top-right.dropdown-anchor-top-right .dropdown-anchor {
+ left:calc(100% - 28px)
+}
+.dropdown-menu.dropdown-anchor-right-top:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-right-center:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-right-bottom:not(.dropdown-overlay-trigger) {
+ margin-left:-10px
+}
+.dropdown-menu.dropdown-anchor-right-top .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-bottom .dropdown-anchor {
+ border-top-color:transparent;
+ border-right-color:transparent;
+ border-bottom-color:transparent;
+ left:100%
+}
+.dropdown-menu.dropdown-anchor-right-top .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-right-center .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-right-bottom .dropdown-anchor::after {
+ border-top-color:transparent;
+ border-right-color:transparent;
+ border-bottom-color:transparent;
+ margin-left:-7px;
+ margin-top:-6px
+}
+.dropdown-menu.dropdown-anchor-right-top.dropdown-anchor-right-top .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-center.dropdown-anchor-right-top .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-bottom.dropdown-anchor-right-top .dropdown-anchor {
+ top:11px
+}
+.dropdown-menu.dropdown-anchor-right-top.dropdown-anchor-right-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-center.dropdown-anchor-right-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-bottom.dropdown-anchor-right-center .dropdown-anchor {
+ top:calc(50% - 7px)
+}
+.dropdown-menu.dropdown-anchor-right-top.dropdown-anchor-right-bottom .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-center.dropdown-anchor-right-bottom .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-right-bottom.dropdown-anchor-right-bottom .dropdown-anchor {
+ top:calc(100% - 26px)
+}
+.dropdown-menu.dropdown-anchor-bottom-left:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-bottom-center:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-bottom-right:not(.dropdown-overlay-trigger) {
+ margin-top:-10px
+}
+.dropdown-menu.dropdown-anchor-bottom-left .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-right .dropdown-anchor {
+ border-right-color:transparent;
+ border-bottom-color:transparent;
+ border-left-color:transparent;
+ top:100%
+}
+.dropdown-menu.dropdown-anchor-bottom-left .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-bottom-center .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-bottom-right .dropdown-anchor::after {
+ border-right-color:transparent;
+ border-bottom-color:transparent;
+ border-left-color:transparent;
+ margin-top:-7px;
+ margin-left:-6px
+}
+.dropdown-menu.dropdown-anchor-bottom-left.dropdown-anchor-bottom-left .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-center.dropdown-anchor-bottom-left .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-right.dropdown-anchor-bottom-left .dropdown-anchor {
+ left:15px
+}
+.dropdown-menu.dropdown-anchor-bottom-left.dropdown-anchor-bottom-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-center.dropdown-anchor-bottom-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-right.dropdown-anchor-bottom-center .dropdown-anchor {
+ left:calc(50% - 7px)
+}
+.dropdown-menu.dropdown-anchor-bottom-left.dropdown-anchor-bottom-right .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-center.dropdown-anchor-bottom-right .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-bottom-right.dropdown-anchor-bottom-right .dropdown-anchor {
+ left:calc(100% - 28px)
+}
+.dropdown-menu.dropdown-anchor-left-top:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-left-center:not(.dropdown-overlay-trigger),
+.dropdown-menu.dropdown-anchor-left-bottom:not(.dropdown-overlay-trigger) {
+ margin-left:10px
+}
+.dropdown-menu.dropdown-anchor-left-top .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-bottom .dropdown-anchor {
+ border-top-color:transparent;
+ border-bottom-color:transparent;
+ border-left-color:transparent;
+ left:-14px
+}
+.dropdown-menu.dropdown-anchor-left-top .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-left-center .dropdown-anchor::after,
+.dropdown-menu.dropdown-anchor-left-bottom .dropdown-anchor::after {
+ border-top-color:transparent;
+ border-bottom-color:transparent;
+ border-left-color:transparent;
+ margin-left:-5px;
+ margin-top:-6px
+}
+.dropdown-menu.dropdown-anchor-left-top.dropdown-anchor-left-top .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-center.dropdown-anchor-left-top .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-bottom.dropdown-anchor-left-top .dropdown-anchor {
+ top:11px
+}
+.dropdown-menu.dropdown-anchor-left-top.dropdown-anchor-left-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-center.dropdown-anchor-left-center .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-bottom.dropdown-anchor-left-center .dropdown-anchor {
+ top:calc(50% - 7px)
+}
+.dropdown-menu.dropdown-anchor-left-top.dropdown-anchor-left-bottom .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-center.dropdown-anchor-left-bottom .dropdown-anchor,
+.dropdown-menu.dropdown-anchor-left-bottom.dropdown-anchor-left-bottom .dropdown-anchor {
+ top:calc(100% - 26px)
+}
+.dropdown-menu.max-height ul {
+ max-height:184px
+}
+.dropdown-menu table td{
+    color:#555;
+    background: #e4f1fb url("images/ui-bg_glass_100_e4f1fb_1x400.png") 50% 50% repeat-x;
+    border-radius: 2pt;
+    padding:0px;
+    margin:2px;
+     text-align: left;
+ vertical-align: text-top;
+}
+.dropdown-menu tr td ul {
+ min-width:140px;
+ list-style:none;
+ background: #d7ebf9 url("images/ui-bg_glass_80_d7ebf9_1x400.png") 50% 50% repeat-x;
+ box-shadow:0 3px 5px rgba(0,0,0,0.1);
+ padding:0;
+ margin:1px;
+}
+.dropdown-menu ul li {
+ list-style:none;
+ padding:0;
+ margin:0;
+ line-height:18px;
+}
+.dropdown-menu ul li>a,
+.dropdown-menu ul li label {
+ display:block;
+ color:#000;
+ text-decoration:none;
+ line-height:18px;
+ padding:5px 15px;
+ white-space:nowrap;
+ transition:all 0.1s;
+}
+.dropdown-menu ul li>a svg,
+.dropdown-menu ul li label svg {
+ height:14px;
+ width:18px;
+ vertical-align:middle;
+ margin-left:-2px;
+ margin-right:4px;
+ margin-top:-3px
+}
+.dropdown-menu ul li>a svg path,
+.dropdown-menu ul li>a svg polygon,
+.dropdown-menu ul li label svg path,
+.dropdown-menu ul li label svg polygon {
+ transition:fill 0.1s;
+ fill:#0b0b0b
+}
+.dropdown-menu ul li>a span.greenSVG svg path,
+.dropdown-menu ul li>a span.greenSVG svg polygon,
+.dropdown-menu ul li label span.greenSVG svg path,
+.dropdown-menu ul li label span.greenSVG svg polygon {
+ fill:#B7D968
+}
+.dropdown-menu ul li>a .flag,
+.dropdown-menu ul li label .flag {
+ padding-bottom:1px
+}
+.dropdown-menu ul li>a:not(.grey):hover,
+.dropdown-menu ul li>a:hover,
+.dropdown-menu ul li label:not(.grey):hover,
+.dropdown-menu ul li label:hover {
+ background-color:#436581;
+ color:#FFF;
+ cursor:pointer
+}
+.dropdown-menu ul li>a:not(.grey):hover svg path,
+.dropdown-menu ul li>a:not(.grey):hover svg polygon,
+.dropdown-menu ul li>a:hover svg path,
+.dropdown-menu ul li>a:hover svg polygon,
+.dropdown-menu ul li label:not(.grey):hover svg path,
+.dropdown-menu ul li label:not(.grey):hover svg polygon,
+.dropdown-menu ul li label:hover svg path,
+.dropdown-menu ul li label:hover svg polygon {
+ fill:#e4f1fb url("images/ui-bg_glass_100_e4f1fb_1x400.png") 50% 50% repeat-x;
+}
+.dropdown-menu ul li>a.active,
+.dropdown-menu ul li label.active {
+ -webkit-user-select:none;
+ -moz-user-select:none;
+ user-select:none;
+ cursor:default;
+ pointer-events:none;
+ color:#999;
+ background:#745cfc
+}
+.dropdown-menu ul li>a.grey:hover,
+.dropdown-menu ul li label.grey:hover {
+ cursor:default
+}
+.dropdown-menu ul li>a.disabled,
+.dropdown-menu ul li>a.disabled:hover,
+.dropdown-menu ul li>a.disabled:active,
+.dropdown-menu ul li label.disabled,
+.dropdown-menu ul li label.disabled:hover,
+.dropdown-menu ul li label.disabled:active {
+ -webkit-user-select:none;
+ -moz-user-select:none;
+ user-select:none;
+ cursor:default;
+ pointer-events:none;
+ cursor:default;
+ color:#999
+}
+.dropdown-menu ul li.title {
+ padding:7.5px 15px;
+ background:#f6f6f6;
+ color:#999;
+ font-family:"Roboto","Open Sans",sans-serif;
+ font-size:12px;
+ text-transform:uppercase;
+ border:0;
+ border-top-left-radius:3px;
+ border-top-right-radius:3px
+}
+.dropdown-menu ul li.title.grey {
+ background:#999;
+ color:#F57C00
+}
+.dropdown-menu ul li.title.light {
+ background:#f6f6f6;
+ color:#fccfa2
+}
+.dropdown-menu ul li.title.accent {
+ background:#F57C00;
+ color:#fccfa2
+}
+.dropdown-menu ul li>a:hover small.grey {
+ opacity:0.75;
+ color:#fff
+}
+.dropdown-menu ul li:first-child.divider {
+ display:none
+}
+.dropdown-menu.right-aligned {
+ text-align:right
+}
+.dropdown-menu.right-aligned ul li a svg {
+ margin-right:0;
+ margin-left:4px
+}
+.dropdown-menu .divider {
+ height:1px;
+ border:6px solid #e4f1fb url("images/ui-bg_glass_100_e4f1fb_1x400.png") 50% 50% repeat-x;
+ margin:5px 1px;
+ overflow:hidden
+}
+.dropdown-menu.dark.assign-dropdown ul li a {
+ color:#fff
+}
+.dropdown-menu.dark.assign-dropdown ul li a:hover {
+ background:#F57C00;
+ color:#fff
+}
+.dropdown-menu.dark ul {
+ background:#32363F
+}
+.dropdown-menu.dark ul li>a,
+.dropdown-menu.dark ul li label {
+ color:#939aaa
+}
+.dropdown-menu.dark ul li>a svg path,
+.dropdown-menu.dark ul li>a svg polygon,
+.dropdown-menu.dark ul li label svg path,
+.dropdown-menu.dark ul li label svg polygon {
+ fill:#778093
+}
+.dropdown-menu.dark ul li>a.active,
+.dropdown-menu.dark ul li label.active {
+ color:#7f889a;
+ background:#393d48
+}
+.dropdown-menu.dark ul li>a.active.green,
+.dropdown-menu.dark ul li label.active.green {
+ background:#B7D968;
+ color:#fff
+}
+.dropdown-menu.dark ul li>a.active.green svg path,
+.dropdown-menu.dark ul li>a.active.green svg polygon,
+.dropdown-menu.dark ul li label.active.green svg path,
+.dropdown-menu.dark ul li label.active.green svg polygon {
+ fill:#fff
+}
+.dropdown-menu.dark ul li>a.active.accent,
+.dropdown-menu.dark ul li label.active.accent {
+ background:#F57C00;
+ color:#fff
+}
+.dropdown-menu.dark ul li>a.active.accent svg path,
+.dropdown-menu.dark ul li>a.active.accent svg polygon,
+.dropdown-menu.dark ul li label.active.accent svg path,
+.dropdown-menu.dark ul li label.active.accent svg polygon {
+ fill:#fff
+}
+.dropdown-menu.dark ul .divider {
+ background:rgba(255,255,255,0.08)
+}
+@media screen and (max-width: 420px) {
+ .dropdown-menu ul li>a {
+  line-height:32px;
+  padding-left:24px;
+  padding-right:24px
+ }
+}
diff --git a/htdocs/cgi-bin/wsrc/jquery.sweet-dropdown.js b/htdocs/cgi-bin/wsrc/jquery.sweet-dropdown.js
new file mode 100644 (file)
index 0000000..53927a0
--- /dev/null
@@ -0,0 +1,11 @@
+/*!
+ * SweetDropdown: Sweet and versatile dropdowns
+ * v1.0.0, 2017-04-09
+ * http://github.com/adeptoas/sweet-dropdown
+ *
+ * Copyright (c) 2016 Adepto.as AS Â· Oslo, Norway
+ * Dual licensed under the MIT and GPL licenses.
+ *
+ * See LICENSE-MIT.txt and LICENSE-GPL.txt
+ */
+!function($){var showDropdown;return $.fn.sweetDropdown=function(method,data){switch(method){case"attach":return $(this).attr("data-dropdown",data);case"detach":return $(this).removeAttr("data-dropdown");case"show":return $(this).click();case"hide":return $.sweetDropdown.hideAll(),$(this);case"enable":return $(this).removeClass("dropdown-disabled");case"disable":return $(this).addClass("dropdown-disabled")}},$.sweetDropdown=function(){},$.sweetDropdown.attachAll=function(){return $("body").off("click.dropdown").on("click.dropdown","[data-dropdown]",showDropdown),$("[data-dropdown]").off("click.dropdown").on("click.dropdown",showDropdown),$("html, .sweet-modal-content").off("click.dropdown").on("click.dropdown",$.sweetDropdown.hideAll),$(window).off("resize.dropdown").on("resize.dropdown",$.sweetDropdown.hideAll),!0},$.sweetDropdown.hideAll=function(e,hideException){var animTimeout,el,hideExceptionID,targetGroup,trigger;return null==e&&(e=null),null==hideException&&(hideException=null),targetGroup=e?$(e.target).parents().addBack():null,targetGroup&&targetGroup.hasClass("dropdown-menu")&&!targetGroup.is("A")?void 0:(el=".dropdown-menu",trigger="[data-dropdown]",hideExceptionID="",hideException&&(hideExceptionID=$(hideException).attr("id"),$('[data-dropdown="#'+hideExceptionID+'"]').hasClass("dropdown-open")||(el=".dropdown-menu:not(#"+hideExceptionID+")",trigger='[data-dropdown!="#'+hideExceptionID+'"]')),$("body").find(el).removeClass("dropdown-opened").end().find(trigger).removeClass("dropdown-open"),animTimeout=window.setTimeout(function(){return $("body").find(el).hide().end()},200),!0)},$.sweetDropdown.ANCHOR_POSITIONS=["top-left","top-center","top-right","right-top","right-center","right-bottom","bottom-left","bottom-center","bottom-right","left-top","left-center","left-bottom"],$.sweetDropdown.defaults={anchorPosition:"center"},showDropdown=function(e){var $anchor,$dropdown,$trigger,addAnchorX,addAnchorY,addX,addY,anchorPosition,anchorSide,bottomTrigger,hasAnchor,heightDropdown,heightTrigger,i,isDisabled,isOpen,left,leftTrigger,len,position,positionParts,ref,rightTrigger,top,topTrigger,widthDropdown,widthTrigger;if(null==e&&(e=null),$trigger=$(this),$dropdown=$($trigger.data("dropdown")),$anchor=$dropdown.find(".dropdown-anchor"),hasAnchor=$dropdown.hasClass("dropdown-has-anchor"),isOpen=$trigger.hasClass("dropdown-open"),isDisabled=$trigger.hasClass("dropdown-disabled"),widthDropdown=$dropdown.outerWidth(),widthTrigger=$trigger.outerWidth(),heightDropdown=$dropdown.outerHeight(),heightTrigger=$trigger.outerHeight(),topTrigger=$trigger.position().top,leftTrigger=$trigger.position().left,$trigger.hasClass("dropdown-use-offset")&&(topTrigger=$trigger.offset().top,leftTrigger=$trigger.offset().left),bottomTrigger=topTrigger+heightTrigger,rightTrigger=leftTrigger+widthTrigger,$dropdown.length<1)return console.error("[SweetDropdown] Could not find dropdown: "+$(this).data("dropdown"));if($anchor.length<1&&hasAnchor&&($anchor=$('<div class="dropdown-anchor"></div>'),$dropdown.prepend($anchor)),void 0!==e&&(e.preventDefault(),e.stopPropagation()),isOpen||isDisabled)return!1;for($.sweetDropdown.hideAll(null,$trigger.data("dropdown")),anchorPosition=$.sweetDropdown.defaults.anchorPosition,ref=$.sweetDropdown.ANCHOR_POSITIONS,i=0,len=ref.length;len>i;i++)position=ref[i],$dropdown.hasClass("dropdown-anchor-"+position)&&(anchorPosition=position);if(top=0,left=0,positionParts=anchorPosition.split("-"),anchorSide=positionParts[0],anchorPosition=positionParts[1],"top"===anchorSide||"bottom"===anchorSide)switch(anchorPosition){case"left":left=leftTrigger;break;case"center":left=leftTrigger-widthDropdown/2+widthTrigger/2;break;case"right":left=rightTrigger-widthDropdown}if("left"===anchorSide||"right"===anchorSide)switch(anchorPosition){case"top":top=topTrigger;break;case"center":top=topTrigger-heightDropdown/2+heightTrigger/2;break;case"bottom":top=topTrigger+heightTrigger-heightDropdown}switch(anchorSide){case"top":top=topTrigger+heightTrigger;break;case"right":left=leftTrigger-widthDropdown;break;case"bottom":top=topTrigger-heightDropdown;break;case"left":left=leftTrigger+widthTrigger}return addX=parseInt($dropdown.data("add-x")),addY=parseInt($dropdown.data("add-y")),isNaN(addX)||(left+=addX),isNaN(addY)||(top+=addY),addAnchorX=parseInt($trigger.data("add-anchor-x")),addAnchorY=parseInt($trigger.data("add-anchor-y")),isNaN(addAnchorX)||$anchor.css({marginLeft:addAnchorX}),isNaN(addAnchorY)||$anchor.css({marginTop:addAnchorY}),$dropdown.css({top:top,left:left,display:"block"}),window.setTimeout(function(){return $dropdown.addClass("dropdown-opened")},0),$trigger.addClass("dropdown-open"),$trigger},$(function(){return $.sweetDropdown.attachAll()})}(jQuery);
\ No newline at end of file
index 760c382933ddc719cb68694556d1b2e933b77953..0ab641459026c51a17723b63342f13403e54e875 100644 (file)
@@ -50,19 +50,66 @@ th,
     text-align: left;
 }
 
+#cat_desc {
+    position: absolute;
+    border: 2px solid #94cde7;
+    padding: 5px;
+    text-align: center;
+    background: #ccffff;
+    margin-top: 15px;
+    margin-left: 200px;
+    width: 400px;
+    text-decoration-style: wavy;
+
+}
+.span_cat {
+    border:1px solid #b2f8ef;
+    border-bottom:solid  #42e4fa 1px;
+    border-style: inset;
+    padding: 2px;
+    padding-right: 10px;
+    padding-left: 10px;
+    width:unset;
+}
+#cat_lst {
+    visibility: hidden;
+}
+
 .r0 {
     background-color: #e6ffff;
     border: 1px solid black;
     border-right: 1px solid black;
     vertical-align: top;
+    apadding:2px;
 }
-
 .r1 {
     background-color: #ccffff;
     border: 1px solid black;
     border-right: 1px solid black;
     vertical-align: top;
 }
+.r2 {
+    background-color: #ccfff0;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+.r3 {
+    background-color: #b2f8ef;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+.rz {
+    border: 1px solid black;
+    text-align: left;
+    background-color: #e6ffff;
+    vertical-align: top;
+    align-self: baseline;
+    height: auto;
+    padding: 2%;
+    margin: 0px auto;
+}
 
 .hdr {
     font-style: normal;
@@ -93,16 +140,7 @@ th,
     padding-left: 5px;
 }
 
-.rz {
-    border: 1px solid black;
-    text-align: left;
-    background-color: #e6ffff;
-    vertical-align: top;
-    align-self: baseline;
-    height: auto;
-    padding: 2%;
-    margin: 0px auto;
-}
+
 
 div#rz {
     border: 1px solid black;
@@ -116,7 +154,7 @@ div#rz {
 }
 
 #tag_FRM {
-    border: 2px solid rgb(148, 205, 231);
+    border: 2px solid #94cde7;
     padding: 5px;
     text-align: right;
     vertical-align: top;
@@ -127,17 +165,6 @@ img {
     padding: 0 10px
 }
 
-#cat_desc {
-    position: absolute;
-    border: 2px solid #94cde7;
-    padding: 5px;
-    text-align: center;
-    background: #ccffff;
-    margin-top: 15px;
-    width: 280px;
-    text-decoration-style: wavy;
-}
-
 #menu {
     position: fixed;
     float: left;
@@ -149,10 +176,6 @@ img {
     margin-left: 90%;
 }
 
-#cat_lst {
-    visibility: hidden;
-}
-
 #frm_login {
     vertical-align: middle;
     margin: 0;
@@ -253,5 +276,5 @@ a:hover {
     font-size: large;
     font-style: normal;
     font-weight: bold;
-    color:crimson; 
+    color:crimson;
 }
\ No newline at end of file
index 4bbb0fd0df44db6060cd9fd203fbe5dd97671844..d1e5a79c13e3e8b5680ef214a746deba33ffb205 100644 (file)
@@ -2,8 +2,10 @@
  Programed by: Will Budic
  Open Source License -> https://choosealicense.com/licenses/isc/
 */
-
-var _MAP = new Map();
+//TODO This mapping is not really necassary twice. Data objects should be mapped not strings.
+//
+//var _CATS_DESC_MAP = new Map();
+//var _CATS_NAME_MAP = new Map();
 var MNU_SCROLLING = false;
 
 var QUILL, QUILL_PNL;
@@ -30,6 +32,7 @@ function onBodyLoad(toggle, tz, today, expires, rs_cur) {
     TIMEZONE   = tz;
     TIME_STAMP = new Date(today);
     onBodyLoadGeneric();
+
     if (toggle) {
         this.toggle("#div_srh", false);
     }
@@ -125,12 +128,6 @@ function onBodyLoad(toggle, tz, today, expires, rs_cur) {
 
     $("#log_submit").click(encodeText);
 
-
-    var kidz = $("#cat_lst").children();
-    for (var i = 0; i < kidz.length; i++) {
-        _MAP.set(kidz[i].id, kidz[i].innerHTML);
-    }
-
     $('#ec').show();
 
     $("#RTF").prop("checked", false);
@@ -173,10 +170,113 @@ function onBodyLoad(toggle, tz, today, expires, rs_cur) {
             }
           }});
     }
+
+    jQuery.fn.dispPos = function () {
+        this.css("position","absolute");
+        this.css("top", Math.max(0, (($(window).height() - $(this).outerHeight())-320 ) +
+                                                    $(window).scrollTop()) + "px");
+        this.css("left", Math.max(0, (($(window).width() - $(this).outerWidth()) / 6) - 100 +
+                                                    $(window).scrollLeft()) + "px");
+        this.css( "zIndex", 8 );
+        return this;
+    }
+
+    jQuery.fn.dropdownPos = function (e,desc) {
+        var pnl = $("#cat_desc");
+        var top = e.css('top');
+        var height= e.css('height');
+        var width = e.css('width');
+        var left = e.css('left');
+        var pwidth = pnl.css('width');
+            top = parseInt(top.replace(/px/, ""));
+            height = parseInt(height.replace(/px/, ""));
+            width = parseInt(width.replace(/px/, ""));
+            left = parseInt(left.replace(/px/, ""));
+            pwidth = parseInt(pwidth.replace(/px/, ""));
+            top += height - 5;
+            left -= (pwidth/2);
+            //pnl.html("["+left+","+top+","+height+","+width+"]"+desc);
+            pnl.html(desc);
+            pnl.css('top', top+'px');
+            pnl.css('left', left+'px');
+            pnl.show();
+    }
+
+
+    $("#dropdown-standard a").click(function(e){
+        e.preventDefault();
+        var ci = $(event.target).parent(); ci = ci.attr('id');
+        var lbl = $(e.target).text();
+        lbl = lbl.replace(/\s*$/g, "");
+        lbl = lbl + "&nbsp;".repeat(16-lbl.length);
+        $("#lcat").html(lbl);
+        $("#ec").val(ci);
+        $("#cat_desc").show();
+    }).mouseenter(function(e){
+        var pr = $(event.target).parent(); pr = pr.attr('id');
+        if(pr){
+            var pnl = $("#cat_desc");
+            var desc = $("meta[id='cats["+pr+"]']").attr('content');
+            $.fn.dropdownPos($("#dropdown-standard"), desc);
+        }
+    }).mouseleave(function(e){$("#cat_desc").hide();});
+
+
+    $("#dropdown-standard-v a").click(function(e){
+        e.preventDefault();
+        var ci = $(event.target).parent(); ci = ci.attr('id');
+        var lbl = $(e.target).text();
+        lbl = lbl.replace(/\s*$/g, "");
+        lbl = lbl + "&nbsp;".repeat(16-lbl.length);
+        $("#lcat_v").html(lbl);
+        $("#vc").val(ci);
+        $("#cat_desc").show();
+    }).mouseenter(function(e){
+        var pr = $(event.target).parent(); pr = pr.attr('id');
+        if(pr){
+            var pnl = $("#cat_desc");
+            var desc = $("meta[id='cats["+pr+"]']").attr('content');
+            $.fn.dropdownPos($("#dropdown-standard-v"), desc);
+        }
+    }).mouseleave(function(e){$("#cat_desc").hide();});
+
+
+
+    $("#dropdown-standard-x a").click(function(e){
+        e.preventDefault();
+        var ci = $(event.target).parent(); ci = ci.attr('id');
+        var lbl = $(e.target).text();
+        lbl = lbl.replace(/\s*$/g, "");
+        lbl = lbl + "&nbsp;".repeat(16-lbl.length);
+        $("#lcat_x").html(lbl);
+        $("#xc").val(ci);
+        $("#cat_desc").show();
+    }).mouseenter(function(e){
+        var pr = $(event.target).parent(); pr = pr.attr('id');
+        if(pr){
+            var pnl = $("#cat_desc");
+            var desc = $("meta[id='cats["+pr+"]']").attr('content');
+            $.fn.dropdownPos($("#dropdown-standard-x"), desc);
+        }
+    }).mouseleave(function(e){$("#cat_desc").hide();});
+
+    $( "#dlgValidation" ).dialog({
+        dialogClass: "alert",
+        buttons: [
+          {
+            text: "OK",
+            click: function() {
+              $( this ).dialog( "close" );
+            }
+          }
+        ]
+      });
+
+
     setPageSessionTimer(expires);
+    display("Log page is ready!");
 }
 
-
 function encodeText(el){
     var el = $("#frm_entry [name=log]");
     var txt = el.val();
@@ -185,68 +285,77 @@ function encodeText(el){
     el.val(txt);
 }
 
-
 function formValidation() {
-    if ($("#ec option:selected").val() == 0) {
-        alert("Category -> has not been selected!");
-        return false;
+    // if ($("#ec option:selected").val() == 0) {
+    //     alert("Category -> has not been selected!");
+    //     return false;
+    // }
+    var dt = $("#frm_entry [name='date']").val();
+    var i = dt.indexOf('id=');
+    if(i>0){
+        dt = dt.substring(0, i-1);
+        $("#frm_entry [name='date']").val(dt);
     }
-    return validDate($("#frm_entry [name='date']").val()) && validLog($("#frm_entry [name='log']").val());
+    return validate(dt, $("#frm_entry [name='log']").val());
 }
-
 function formDelValidation() {
 
 }
 
-
-function validDate(dt) {
+function validate(dt, log) {
+    var tm, msg;
     if (!Date.parse(dt)) {
-        alert("Date -> '" + dt + "' is Invalid can't submit!");
-        return false;
+        msg = "<b>Date</b> field entry -> " + dt + " is Invalid can't submit!<br>";
+    }
+    else{
+        tm = validTime(dt.substring(dt.indexOf(" ") + 1));
+        if(tm){
+            msg = "<b>Date</b> field entry wrong time -> " + tm;
+        }
+        else{
+            msg = "";
+        }
+    }
+    if ($("#ec").val() == 0) {
+        msg =  msg + "<b>Category</b> field selection hasn't been made!<br>";
+    }
+    if (!log) {
+        msg = msg + "<b>Log</b> field entry can't be empty, can't submit!<br>";
+    }
+    if(msg){
+        return dialogModal( "Sorry Form Validation Failed", msg);
     }
-    return validTime(dt.substring(dt.indexOf(" ") + 1));
 }
 
 function validTime(val) {
     // regular expression to match required time format
     re = /^(\d{2}):(\d{2}):(\d{2})([ap]m)?$/;
     var fld = $("frm_entry").date;
+    var msg;
     if (val != '') {
         if (regs = val.match(re)) {
             // 12-hour value between 0 and 24
             if (regs[1] < 0 || regs[1] > 23) {
-                alert("Invalid value for hours: " + regs[1]);
+                msg += " Invalid value for hours: " + regs[1];
                 fld.focus();
-                return false;
             }
             // minute value between 0 and 59
             if (regs[2] > 59) {
-                alert("Invalid value for minutes: " + regs[2]);
+                msg += " Invalid value for minutes: " + regs[2];
                 fld.focus();
-                return false;
             }
             // seconds value between 0 and 59
             if (regs[3] > 59) {
-                alert("Invalid value for seconds: " + regs[2]);
-                fld.focus();
-                return false;
+                msg += " Invalid value for seconds: " + regs[2];
             }
         } else {
-            alert("Invalid time format: " + val);
+            msg = "Invalid time format: " + val;
             fld.focus();
-            return false;
         }
-        return true;
+        return msg;
     }
 }
 
-function validLog(log) {
-    if (log == "") {
-        alert("Log -> entry can't be empty, can't submit!");
-        return false;
-    }
-    return true;
-}
 
 
 function setNow() {
@@ -328,12 +437,19 @@ function edit(row) {
     $("#STICKY").prop('checked', isSticky);
 
     if(isRTF){
+        display("Loading RTF: "+ ed_v.val() );
         loadRTF(false, row);
-    }
+    }else{display("Editing: "+ ed_v.val(),3);}
 
     //Select category
-    var ec_v = $("#c" + row).text();
-    $("#ec option:contains(" + ec_v + ")").prop('selected', true);
+    var ec_lb = $("#c" + row).text();
+    var ec_id = $("meta[name='"+ec_lb+"']").attr('id');
+    ec_id = ec_id.replace(/^cats\[/g,'');
+    ec_id = ec_id.replace(/\]$/g,'');
+    $("#lcat").html(ec_lb);
+    $("#ec").val(ec_id);
+
+
     $("#submit_is_edit").val(row);
 
     //Amount type
@@ -588,20 +704,25 @@ function showAll() {
     return false;
 }
 
-function helpSelCategory(sel) {
+// function helpSelCategory(sel) {
+
+//     var desc = _CATS_DESC_MAP.get(sel.options[sel.selectedIndex].value);
+//     if (!desc) {
+//         desc = "<font color='red'>Please select a Category!</font>";
+//     }
+//     display(desc);
+// }
 
-    var desc = _MAP.get(sel.options[sel.selectedIndex].value);
-    if (!desc) {
-        desc = "<font color='red'>Please select a Category!</font>";
-    }
-    display(desc);
-}
 
-function display(desc){
+function display(desc, times){
     var pnl = $("#cat_desc");
+    if(!times){
+        times = 1;
+    }
     pnl.html(desc);
+    pnl.dispPos(true);
     pnl.show();
-    pnl.fadeOut(5000);
+    pnl.fadeOut(1000*times);
 }
 
 function viewByCategory(btn) {
@@ -611,63 +732,71 @@ function viewByCategory(btn) {
 function viewExcludeCategory(btn) {
     $("#rs_keys").value = "";
     $("#vc").value = "0";
-    var tagged = $('#divxc').text();
-    if(tagged.length>0){
-        var opts = $("#xc option");
-        var ids = "";
-        for(var i =0; i < opts.length; i++){
-                var lbl = opts[i].innerText;
-                if(tagged.match(lbl)){
-                    ids += opts[i].value + ',';
-                }
-
-        }
-        $("#idx_cat_x").val(ids.replace(/^\,+|\,+$/g,''));
-    }
 }
+
 function addExclude() {
-    var sel = $('#xc option:selected');
+    var xc = $("#xc").val();
+    var xlst = $("#xclst");
+    if(xc == 0){
+        return dialogModal("Can't add exclude!", "Must select a category to add to list of excludes.");
+    }
+
+    var sel = $("meta[id='cats["+xc+"]']").attr('name'); //_CATS_NAME_MAP.get(ix.val());//$('#xc option:selected');
     var div = $('#divxc');
     var tagged = $('#divxc').text();
-    var reg = new RegExp(sel.text());
-    if($('#xc').val() == 0){
-        alert("Must select a category to add to list of excludes.");
-    }
-    else if(!tagged.match(reg)){
+    var reg = new RegExp(sel);
+
+    if(!tagged.match(reg)){
+        $('#divxc_lbl').show();
         if(tagged.length>0){
-            div.text(tagged + ',' + sel.text());
+            div.text(tagged + ',' + sel);
+            xlst.val(xlst.val() + ',' + xc);
         }
         else{
-            $('#divxc_lbl').toggle();
-            div.text(sel.text());
+            div.text(sel);
+            xlst.val(xc);
         }
     }
 
-
 return false;
 }
+
 function removeExclude() {
-    var sel = $('#xc option:selected');
+    var xc = $("#xc").val();
+    var xlst = $("#xclst");
+    if(xc == 0){
+        return dialogModal("Can't remove exclude!", "Must select a category to add to list of excludes.");
+    }
+    var sel = $("meta[id='cats["+xc+"]']").attr('name'); //_CATS_NAME_MAP.get(xc.val());
+    //var sel = $('#xc option:selected');
     var div = $('#divxc');
     var tagged = $('#divxc').text();
-    var reg = new RegExp(sel.text());
-
-    if($('#xc').val() == 0){
-        alert("Must select a category to remove from list of excludes.");
-    }
-    else if(tagged.match(reg)){
+    var tagids = xlst.val();
+    var reg = new RegExp(sel);
+    if(tagged.match(reg)){
             tagged = tagged.replace(reg,'');
             tagged = tagged.replace(/\,\,/,'\,');
             tagged = tagged.replace(/^\,+|\,+$/g,'');
-            div.text(tagged);
+            tagids = tagids.replace(xc,'');
+            tagids = tagids.replace(/\,\,/,'\,');
+            tagids = tagids.replace(/^\,+|\,+$/g,'');
             if(tagged.length==0){
-                $('#divxc_lbl').toggle();
+                $('#divxc_lbl').hide();
             }
+            div.text(tagged);
+            xlst.val(tagids);
     }
 
 return false;
 }
 
+function resetExclude(){
+    $("#xc").val(0);
+    $('#divxc').text("");
+    $("#lcat_x").html("&nbsp;&nbsp;&nbsp;<font size=1>-- Select --</font></i>&nbsp;&nbsp;&nbsp;");
+    $("#xclst").val("");
+}
+
 function viewByDate(btn) {
     // alert(btn.value);
 }
@@ -819,7 +948,7 @@ function loadRTFResult(content, result, prms, quill) {
     else{
         var id = json.content.lid;
         var css = $("#q-scroll"+id).prop('style');
-        css.backgroundColor = json.content.bg
+        if(css){css.backgroundColor = json.content.bg}
     }
     //alert(obj.response);
 }
@@ -854,6 +983,38 @@ function RGBToHex(rgb) {
     return "#" + r + g + b;
 }
 
+function fetchBackup() {
+    window.location = "config.cgi?bck=1";
+}
+function deleteBackup() {
+    $('<div></div>').dialog({
+        modal: true,
+        title: "Please Confirm?",
+        width: "40%",
+        show: { effect: "clip", duration: 1000 },
+        hide: { effect: "explode", duration: 1000},
+        open: function() {
+            var sel = $("#bck input[type=radio]:checked").val();
+          $(this).html("Are you sure you want to delete file:<br><b>"+sel+"</b>");
+        },
+        buttons: [
+             {  text: "Yes",
+                icon: "ui-icon-trash",
+                click: function() {
+                  $( this ).dialog( "close" );
+                    var sel = $( "#bck input[type=radio]:checked").val();
+                    window.location = "config.cgi?bck_del="+sel+"#backup";
+                }
+              },
+
+            { text: "Cancel",
+                click: function() { $( this ).dialog( "close" );
+                return false;
+                }
+            }
+        ]
+    });
+}
 
 function exportToCSV(dat, view){
     var csv;
@@ -887,8 +1048,7 @@ function setPageSessionTimer(expires) {
                 var sec = ((dif % 60000) / 1000).toFixed(0);
                 var out = (min < 10 ? '0' : '') + min + ":" + (sec < 10 ? '0' : '') + sec;
                 var tim = new moment().tz(TIMEZONE).format("hh:mm:ss a");
-                $("#sss_status").html("Current Time:" + tim + " Session expires in " + out);
-                //$("#sss_status").html(" Session expires  " + timeout.from(now));//timeout.format("ddd, hA, HH:mm:ss"));
+                $("#sss_status").html("<font size='1px'>[" + tim + "]</font> Session expires in " + out);
                 if(now.isAfter(timeout)){
                     $("#sss_status").html("<span id='sss_expired'><a href='login_ctr.cgi'>Page Session has Expired!</a></span>");
                     clearInterval(timer);
@@ -898,3 +1058,30 @@ function setPageSessionTimer(expires) {
 
        }
 
+ function  checkConfigCatsChange(){
+     var e1 = $('#frm_config input[name="caid"]').val();
+     var e2 = $('#frm_config input[name="canm"]').val();
+     if(e1.length>0 && e2.length>0){
+     return dialogModal("Sorry Categories Config Validation Failed",
+                        "Did you fail to clear or add a new category first? ->" + e2);
+     }
+     return true;
+ }
+
+ function dialogModal(title, message) {
+    $('<div></div>').dialog({
+        modal: true,
+        title: title,
+        width: "40%",
+        show: { effect: "clip", duration: 800 },
+        open: function() {
+          $(this).html(message);
+        },
+        buttons: {
+          Ok: function() {
+            $( this ).dialog( "close" );
+          }
+        }
+    });
+return false;
+ }
\ No newline at end of file
index 53fd9ecacc711201afa543bffde64d0e2967501c..fe08c36d0777f29e05fb90caa728a9647c7f8529 100644 (file)
@@ -51,18 +51,39 @@ th,
 }
 
 .r0 {
-    background-color: #e46331;
+    background-color: #d1663c;
     border: 1px solid black;
     border-right: 1px solid black;
     vertical-align: top;
 }
-
 .r1 {
     background-color: #a5682f;
     border: 1px solid black;
     border-right: 1px solid black;
     vertical-align: top;
 }
+.r2 {
+    background-color: #e97f56;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+.r3 {
+    background-color: #ee9440;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+.rz {
+    border: 1px solid black;
+    text-align: left;
+    background-color: #4d9b46;
+    vertical-align: top;
+    align-self: baseline;
+    height: auto;
+    padding: 2%;
+    margin: 0px auto;
+}
 
 .hdr {
     font-style: normal;
@@ -93,21 +114,10 @@ th,
     padding-left: 5px;
 }
 
-.rz {
-    border: 1px solid black;
-    text-align: left;
-    background-color: #118107;
-    vertical-align: top;
-    align-self: baseline;
-    height: auto;
-    padding: 2%;
-    margin: 0px auto;
-}
-
 div#rz {
     border: 1px solid black;
     text-align: left;
-    background-color: #08fa08;
+    background-color: #6ed16e;
     vertical-align: top;
     align-self: baseline;
     height: auto;
@@ -127,22 +137,33 @@ img {
     padding: 0 10px
 }
 
+
 #cat_desc {
     position: absolute;
     border: 2px solid #7a3d03;
     padding: 5px;
     text-align: center;
     background: #c28a4a;
-    margin-top: 15px;
+
+    margin-top: 20px;
+    margin-left: 200px;
     width: 280px;
     text-decoration-style: wavy;
+
+}
+.span_cat {
+    border:1px solid #7a3d03;
+    border-style: outset;
+    padding: 2px;
+    padding-right: 5px;
+    width: 180px;
 }
 
 #menu {
     position: fixed;
     float: left;
     margin: 0;
-    border: 2px solid #118107;
+    border: 2px solid #23771c;
     padding: 5px;
     text-align: center;
     background: #c28a4a;
@@ -162,7 +183,7 @@ img {
 .ui-button,
 .ui-button-text .ui-button {
     font-size: 12px !important;
-    background: #b5e7a1 !important;    
+    background: #b5e7a1 !important;
 }
 
 .ui-menu {
@@ -211,12 +232,12 @@ img {
     max-height: 480px;
     border-top: 1px solid gray;
     border-right: 1px solid gray;
-    background-color: #118107;
+    background-color: #40ad36;
 }
 
 #toolbar-container {
     border: 1px solid black;
-    background-color: #118107;
+    background-color: #40ad36;
     margin-bottom: 3px;
 }
 
index 81e800e95aec1386f712b31b3f09618776060907..6cf65f8d8d89917474d58fb6b55ac0baa76a7fd5 100644 (file)
@@ -1,7 +1,7 @@
 p {
     font-family: Bookman;
     margin-left: 70px;
-    font-weight: bold;    
+    font-weight: bold;
 }
 pre{
     color: #d8d8d8;
@@ -21,8 +21,8 @@ td {
 div {
     font-family: Bookman;
     text-align: center;
-    vertical-align: middle; 
-    color: #d8d8d8;  
+    vertical-align: middle;
+    color: #d8d8d8;
 }
 
 #div_srh {
@@ -42,7 +42,7 @@ th,
     padding-bottom: 5px;
     padding-left: 5px;
     margin-bottom: 0px;
-    margin-top: 5px;    
+    margin-top: 5px;
 }
 
 .tbl_rem {
@@ -62,13 +62,46 @@ th,
     border-right: 1px solid white;
     vertical-align: top;
 }
-
 .r1 {
     background-color: #272626;
     border: 1px solid white;
     border-right: 1px solid white;
     vertical-align: top;
 }
+.r2 {
+    background-color: #636969;
+    border: 1px solid white;
+    border-right: 1px solid white;
+    vertical-align: top;
+}
+.r3 {
+    background-color: #918383;
+    border: 1px solid white;
+    border-right: 1px solid white;
+    vertical-align: top;
+}
+.rz {
+    border: 1px solid white;
+    text-align: left;
+    background-color: #363531;
+    color: #cfcfe6;
+    vertical-align: top;
+    align-self: baseline;
+    height: auto;
+    margin: 0px auto;
+    padding:10px;
+}
+div#rz {
+    border: 1px solid white;
+    text-align: left;
+    background-color:#363531;
+    color:#f3f0e6;
+    vertical-align: top;
+    align-self: baseline;
+    height: auto;
+    padding: 2px;
+    margin: 0px auto;
+}
 
 .hdr {
     font-style: normal;
@@ -99,30 +132,6 @@ th,
     padding-left: 5px;
 }
 
-.rz {
-    border: 1px solid white;
-    text-align: left;
-    background-color: #363531;
-    color: #cfcfe6;
-    vertical-align: top;
-    align-self: baseline;
-    height: auto;    
-    margin: 0px auto;
-    padding:10px;
-}
-
-div#rz {
-    border: 1px solid white;
-    text-align: left;
-    background-color:#363531;
-    color:#f3f0e6;
-    vertical-align: top;
-    align-self: baseline;
-    height: auto;
-    padding: 2px;
-    margin: 0px auto;
-}
-
 #tag_FRM {
     border: 2px solid #2b3d46;
     padding: 5px;
@@ -142,9 +151,18 @@ img {
     text-align: center;
     background: #0f0f0f;
     color: #d8d8d8;
-    margin-top: 15px;
+    margin-top: 20px;
+    margin-left: 200px;
     width: 280px;
     text-decoration-style: wavy;
+
+}
+.span_cat {
+    border:1px solid #9bb6c3;
+    border-style: outset;
+    padding: 2px;
+    padding-right: 5px;
+    width: 180px;
 }
 
 #menu {
@@ -153,7 +171,7 @@ img {
     margin: 0;
     border: 2px solid #9bb6c3;
     padding: 5px;
-    text-align: center;    
+    text-align: center;
     margin-left: 90%;
     color: white;
     background-color: #363531;
index 68c65f561b91f92c0a0c85e9062b76b0b75357d9..7cd55c3e7a38bf62fd2de97fe953b3723d30fc34 100644 (file)
@@ -56,14 +56,35 @@ th,
     border-right: 1px solid black;
     vertical-align: top;
 }
-
 .r1 {
-    background-color: #CFB53B;
+    background-color: #FFEA00;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+.r2 {
+    background-color: #ffdd44;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+.r3 {
+    background-color: #ffcf35;
     border: 1px solid black;
     border-right: 1px solid black;
     vertical-align: top;
 }
 
+.rz {
+    border: 1px solid black;
+    text-align: left;
+    background-color: #D4AF37 vertical-align: top;
+    align-self: baseline;
+    height: auto;
+    padding: 2%;
+    margin: 0px auto;
+}
+
 .hdr {
     font-style: normal;
     font-weight: bold;
@@ -93,15 +114,7 @@ th,
     padding-left: 5px;
 }
 
-.rz {
-    border: 1px solid black;
-    text-align: left;
-    background-color: #D4AF37 vertical-align: top;
-    align-self: baseline;
-    height: auto;
-    padding: 2%;
-    margin: 0px auto;
-}
+
 
 div#rz {
     border: 1px solid black;
@@ -126,16 +139,27 @@ img {
     padding: 0 10px
 }
 
+
 #cat_desc {
     position: absolute;
     border: 2px solid black;
     padding: 5px;
     text-align: center;
     background: #D4AF37;
-    margin-top: 15px;
+    margin-top: 20px;
+    margin-left: 200px;
     width: 280px;
     text-decoration-style: wavy;
+
 }
+.span_cat {
+    border:1px solid #b2f8ef;
+    border-style: outset;
+    padding: 2px;
+    padding-right: 5px;
+    width: 180px;
+}
+
 
 #menu {
     position: fixed;
@@ -237,5 +261,5 @@ img {
  font-size: large;
  font-style: normal;
  font-weight: bold;
- color:crimson; 
+ color:crimson;
 }
\ No newline at end of file
index 8058e2273db0748364e107ea815c1e4c6c8c4892..25e7848c3dd85e4c97071195ffe24c7b98c3a951 100755 (executable)
@@ -1,3 +1,3 @@
-#!/bin/bash                                                                                        
-kill -9 $(./htdocs/thttpd.pid)
+#!/bin/bash
+sudo kill -9 $(./log/thttpd.pid)
 
index f8a51e6a2ea7a4a1d8cabad385eca2b6e14295b4..927b65472f7a327dd714d644a6ef1d574ed51c4a 100644 (file)
@@ -2,7 +2,7 @@
 dir=./htdocs
 user=will
 logfile=/home/will/dev/LifeLog/log/thttpd.log
-pidfile=thttpd.pid
+pidfile=/home/will/dev/LifeLog/log/thttpd.pid
 # This section _documents_ defaults in effect
 port=8080
 nosymlinkcheck # default = !chroot