From 3657a6f83fcc13600df122fd14bf3b310227b166 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 10 Jun 2008 15:41:43 +0200 Subject: [PATCH 001/142] Incremented minor Version number --- ChangeLog | 3 +++ src/include/functions_common.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 6399df1..63e2214 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,7 @@ --------------------------------------------------------------------------- +Version 2.5.0 (devel), 2008-06-10 +- Moved older devel branch to beta branch. Increment Version minor number. +--------------------------------------------------------------------------- Version 2.3.6 (devel), 2008-06-09 - Added new feature, multiple configureable views which can be configured and selected for each source seperately. Old configurations can still diff --git a/src/include/functions_common.php b/src/include/functions_common.php index e282e49..2e374ce 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -62,7 +62,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.3.6"; +$content['BUILDNUMBER'] = "2.5.0"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['EXTRA_METATAGS'] = ""; From 07f9244647b61682afe5ce73faf0f6057c8fc8f1 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 11 Jul 2008 11:36:01 +0200 Subject: [PATCH 002/142] Started implementing userdb system. But it isn't fully operateable yet. --- src/include/config.sample.php | 7 +- src/include/functions_common.php | 145 ++++++---------------------- src/include/functions_config.php | 13 --- src/include/functions_db.php | 22 ++--- src/include/functions_users.php | 160 +++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 141 deletions(-) create mode 100644 src/include/functions_users.php diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 1c6e6d7..6c3bb53 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -37,7 +37,11 @@ if ( !defined('IN_PHPLOGCON') ) } // --- -// --- Database options +// --- UserDB options +/* If UserDB is enabled, all options will and have to be configured in the database. +* All Options below the UserDB options here will not be used, unless a setting +* is missing in the database. +*/ $CFG['UserDBEnabled'] = false; $CFG['UserDBServer'] = ""; $CFG['UserDBPort'] = 3306; @@ -45,6 +49,7 @@ $CFG['UserDBName'] = ""; $CFG['UserDBPref'] = ""; $CFG['UserDBUser'] = ""; $CFG['UserDBPass'] = ""; +$CFG['UserDBLoginRequired'] = false; // --- // --- Misc Options diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 21f8287..0567f35 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -90,11 +90,23 @@ function InitBasicPhpLogCon() // Start the PHP Session StartPHPSession(); - + // Init View Configs prior loading config.php! InitViewConfigs(); } +function InitUserSystemPhpLogCon() +{ + // global vars needed + global $CFG, $gl_root_path, $content; + + if ( isset($CFG['UserDBEnabled']) && $CFG['UserDBEnabled'] ) + { + // Include User Functions + include($gl_root_path . 'include/functions_users.php'); + } +} + function InitPhpLogConConfigFile($bHandleMissing = true) { // Needed to make global @@ -106,7 +118,13 @@ function InitPhpLogConConfigFile($bHandleMissing = true) include_once($gl_root_path . 'config.php'); // Easier DB Access - define('DB_CONFIG', $CFG['UserDBPref'] . "config"); + define('DB_CONFIG', $CFG['UserDBPref'] . "config"); + define('DB_GROUPS', $CFG['UserDBPref'] . "groups"); + define('DB_GROUPMEMBERS', $CFG['UserDBPref'] . "groupmembers"); + define('DB_SEARCHES', $CFG['UserDBPref'] . "searches"); + define('DB_SOURCES', $CFG['UserDBPref'] . "sources"); + define('DB_USERS', $CFG['UserDBPref'] . "users"); + define('DB_VIEWS', $CFG['UserDBPref'] . "views"); // Legacy support for old columns definition format! if ( isset($CFG['Columns']) && is_array($CFG['Columns']) ) @@ -175,6 +193,9 @@ function InitPhpLogCon() // Will init the config file! InitPhpLogConConfigFile(); + // Init UserDB related stuff! + InitUserSystemPhpLogCon(); + // Moved here, because we do not need if GZIP needs to be enabled before the config is loaded! InitRuntimeInformations(); @@ -502,11 +523,13 @@ function InitConfigurationValues() $result = DB_Query("SELECT * FROM " . DB_CONFIG); $rows = DB_GetAllRows($result, true, true); + // Read results from DB and overwrite in $CFG Array! if ( isset($rows ) ) { for($i = 0; $i < count($rows); $i++) - $content[ $rows[$i]['name'] ] = $rows[$i]['value']; + $CFG[ $rows[$i]['name'] ] = $rows[$i]['value']; } + // General defaults // --- Language Handling if ( !isset($content['gen_lang']) ) { $content['gen_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; } @@ -931,7 +954,9 @@ function CreateTopLevelDomainSearch() $szTLDDomains .= "aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|cTLD|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw"; } -// --- BEGIN Usermanagement Function --- +/* +* This Functions starts the main PHP Session if necessary +*/ function StartPHPSession() { global $RUNMODE; @@ -946,116 +971,4 @@ function StartPHPSession() } } -function CheckForUserLogin( $isloginpage, $isUpgradePage = false ) -{ - global $content; - - if ( isset($_SESSION['SESSION_LOGGEDIN']) ) - { - if ( !$_SESSION['SESSION_LOGGEDIN'] ) - RedirectToUserLogin(); - else - { - $content['SESSION_LOGGEDIN'] = "true"; - $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; - } - - // New, Check for database Version and may redirect to updatepage! - if ( isset($content['database_forcedatabaseupdate']) && - $content['database_forcedatabaseupdate'] == "yes" && - $isUpgradePage == false - ) - RedirectToDatabaseUpgrade(); - } - else - { - if ( $isloginpage == false ) - RedirectToUserLogin(); - } - -} - -function CreateUserName( $username, $password, $access_level ) -{ - $md5pass = md5($password); - $result = DB_Query("SELECT username FROM " . STATS_USERS . " WHERE username = '" . $username . "'"); - $rows = DB_GetAllRows($result, true); - if ( isset($rows) ) - { - DieWithFriendlyErrorMsg( "User $username already exists!" ); - - // User not created! - return false; - } - else - { - // Create User - $result = DB_Query("INSERT INTO " . STATS_USERS . " (username, password, access_level) VALUES ('$username', '$md5pass', $access_level)"); - DB_FreeQuery($result); - - // Success - return true; - } -} - -function CheckUserLogin( $username, $password ) -{ - global $content, $CFG; - - // TODO: SessionTime and AccessLevel check - - $md5pass = md5($password); - $sqlselect = "SELECT access_level FROM " . STATS_USERS . " WHERE username = '" . $username . "' and password = '" . $md5pass . "'"; - $result = DB_Query($sqlselect); - $rows = DB_GetAllRows($result, true); - if ( isset($rows) ) - { - $_SESSION['SESSION_LOGGEDIN'] = true; - $_SESSION['SESSION_USERNAME'] = $username; - $_SESSION['SESSION_ACCESSLEVEL'] = $rows[0]['access_level']; - - $content['SESSION_LOGGEDIN'] = "true"; - $content['SESSION_USERNAME'] = $username; - - // Success ! - return true; - } - else - { - if ( $CFG['MiscShowDebugMsg'] == 1 ) - DieWithFriendlyErrorMsg( "Debug Error: Could not login user '" . $username . "'

Sessionarray
" . var_export($_SESSION, true) . "

SQL Statement: " . $sqlselect ); - - // Default return false - return false; - } -} - -function DoLogOff() -{ - global $content; - - unset( $_SESSION['SESSION_LOGGEDIN'] ); - unset( $_SESSION['SESSION_USERNAME'] ); - unset( $_SESSION['SESSION_ACCESSLEVEL'] ); - - // Redir to Index Page - RedirectPage( "index.php"); -} - -function RedirectToUserLogin() -{ - // TODO Referer - header("Location: login.php?referer=" . $_SERVER['PHP_SELF']); - exit; -} - -function RedirectToDatabaseUpgrade() -{ - // TODO Referer - header("Location: upgrade.php"); // ?referer=" . $_SERVER['PHP_SELF']); - exit; -} -// --- END Usermanagement Function --- - - ?> \ No newline at end of file diff --git a/src/include/functions_config.php b/src/include/functions_config.php index b9eaac3..5a40404 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -216,20 +216,7 @@ // Loop through views now and copy into content array! foreach ( $CFG['Views'] as $key => $view ) - { $content['Views'][$key] = $view; - - /* - // Set View from session if available! - if ( isset($_SESSION['currentSourceID']) ) - { - $currentSourceID = $_SESSION['currentSourceID']; - - if ( isset($_SESSION[$currentSourceID . "-View"]) && ) - $content['Views'][$key]['selected'] = "selected"; - } - */ - } } /* diff --git a/src/include/functions_db.php b/src/include/functions_db.php index b2e0283..1af7a73 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -45,7 +45,7 @@ $errdesc = ""; $errno = 0; // --- Current Database Version, this is important for automated database Updates! -$content['database_internalversion'] = "1"; // Whenever incremented, a database upgrade is needed +$content['database_internalversion'] = "0"; // Whenever incremented, a database upgrade is needed $content['database_installedversion'] = "0"; // 0 is default which means Prior Versioning Database // --- @@ -54,9 +54,9 @@ function DB_Connect() global $link_id, $CFG; //TODO: Check variables first - $link_id = mysql_connect($CFG['DBServer'],$CFG['User'],$CFG['Pass']); + $link_id = mysql_connect($CFG['UserDBServer'],$CFG['UserDBUser'],$CFG['UserDBPass']); if (!$link_id) - DB_PrintError("Link-ID == false, connect to ".$CFG['DBServer']." failed", true); + DB_PrintError("Link-ID == false, connect to ".$CFG['UserDBServer']." failed", true); // --- Now, check Mysql DB Version! $strmysqlver = mysql_get_server_info(); @@ -78,10 +78,12 @@ function DB_Connect() } // --- - $db_selected = mysql_select_db($CFG['DBName'], $link_id); + $db_selected = mysql_select_db($CFG['UserDBName'], $link_id); if(!$db_selected) - DB_PrintError("Cannot use database '" . $CFG['DBName'] . "'", true); + DB_PrintError("Cannot use database '" . $CFG['UserDBName'] . "'", true); // :D Success connecting to db + + // TODO Do some more validating on the database } function DB_Disconnect() @@ -283,25 +285,23 @@ function DB_Exec($query) function WriteConfigValue($szValue) { // --- Abort in this case! - global $CFG; + global $CFG, $content; if ( $CFG['UserDBEnabled'] == false ) return; // --- - global $content; - $result = DB_Query("SELECT name FROM " . STATS_CONFIG . " WHERE name = '" . $szValue . "'"); $rows = DB_GetAllRows($result, true); if ( !isset($rows) ) { // New Entry - $result = DB_Query("INSERT INTO " . STATS_CONFIG . " (name, value) VALUES ( '" . $szValue . "', '" . $content[$szValue] . "')"); + $result = DB_Query("INSERT INTO " . STATS_CONFIG . " (name, value) VALUES ( '" . $szValue . "', '" . $CFG[$szValue] . "')"); DB_FreeQuery($result); } else { // Update Entry - $result = DB_Query("UPDATE " . STATS_CONFIG . " SET value = '" . $content[$szValue] . "' WHERE name = '" . $szValue . "'"); + $result = DB_Query("UPDATE " . STATS_CONFIG . " SET value = '" . $CFG[$szValue] . "' WHERE name = '" . $szValue . "'"); DB_FreeQuery($result); } } @@ -337,4 +337,4 @@ function GetRowsAffected() -?> +?> \ No newline at end of file diff --git a/src/include/functions_users.php b/src/include/functions_users.php new file mode 100644 index 0000000..3d97383 --- /dev/null +++ b/src/include/functions_users.php @@ -0,0 +1,160 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * UserDB needed functions * + * * + * -> * + * * + * All directives are explained within this file * + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- Basic Includes +//include($gl_root_path . 'include/constants_general.php'); +///include($gl_root_path . 'include/constants_logstream.php'); +// --- + +// --- BEGIN Usermanagement Function --- +function CheckForUserLogin( $isloginpage, $isUpgradePage = false ) +{ + global $content; + + if ( isset($_SESSION['SESSION_LOGGEDIN']) ) + { + if ( !$_SESSION['SESSION_LOGGEDIN'] ) + RedirectToUserLogin(); + else + { + $content['SESSION_LOGGEDIN'] = "true"; + $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; + } + + // New, Check for database Version and may redirect to updatepage! + if ( isset($content['database_forcedatabaseupdate']) && + $content['database_forcedatabaseupdate'] == "yes" && + $isUpgradePage == false + ) + RedirectToDatabaseUpgrade(); + } + else + { + if ( $isloginpage == false ) + RedirectToUserLogin(); + } + +} + +function CreateUserName( $username, $password, $access_level ) +{ + $md5pass = md5($password); + $result = DB_Query("SELECT username FROM " . STATS_USERS . " WHERE username = '" . $username . "'"); + $rows = DB_GetAllRows($result, true); + if ( isset($rows) ) + { + DieWithFriendlyErrorMsg( "User $username already exists!" ); + + // User not created! + return false; + } + else + { + // Create User + $result = DB_Query("INSERT INTO " . STATS_USERS . " (username, password, access_level) VALUES ('$username', '$md5pass', $access_level)"); + DB_FreeQuery($result); + + // Success + return true; + } +} + +function CheckUserLogin( $username, $password ) +{ + global $content, $CFG; + + // TODO: SessionTime and AccessLevel check + + $md5pass = md5($password); + $sqlselect = "SELECT access_level FROM " . STATS_USERS . " WHERE username = '" . $username . "' and password = '" . $md5pass . "'"; + $result = DB_Query($sqlselect); + $rows = DB_GetAllRows($result, true); + if ( isset($rows) ) + { + $_SESSION['SESSION_LOGGEDIN'] = true; + $_SESSION['SESSION_USERNAME'] = $username; + $_SESSION['SESSION_ACCESSLEVEL'] = $rows[0]['access_level']; + + $content['SESSION_LOGGEDIN'] = "true"; + $content['SESSION_USERNAME'] = $username; + + // Success ! + return true; + } + else + { + if ( $CFG['MiscShowDebugMsg'] == 1 ) + DieWithFriendlyErrorMsg( "Debug Error: Could not login user '" . $username . "'

Sessionarray
" . var_export($_SESSION, true) . "

SQL Statement: " . $sqlselect ); + + // Default return false + return false; + } +} + +function DoLogOff() +{ + global $content; + + unset( $_SESSION['SESSION_LOGGEDIN'] ); + unset( $_SESSION['SESSION_USERNAME'] ); + unset( $_SESSION['SESSION_ACCESSLEVEL'] ); + + // Redir to Index Page + RedirectPage( "index.php"); +} + +function RedirectToUserLogin() +{ + // TODO Referer + header("Location: login.php?referer=" . $_SERVER['PHP_SELF']); + exit; +} + +function RedirectToDatabaseUpgrade() +{ + // TODO Referer + header("Location: upgrade.php"); // ?referer=" . $_SERVER['PHP_SELF']); + exit; +} +// --- END Usermanagement Function --- + + +?> \ No newline at end of file From cc5492d4690e0ea502b3a31e899b8a53ddef69d9 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 11 Jul 2008 15:35:10 +0200 Subject: [PATCH 003/142] Implemented login&logout site and function. The engine also reads configuration values from the configuration table if available. Header Menu also enhanced with Login/Logoff links --- src/include/config.sample.php | 1 + src/include/functions_common.php | 69 +++++++++++--------- src/include/functions_db.php | 37 +++++------ src/include/functions_users.php | 67 ++++++++++++------- src/lang/en/main.php | 11 ++++ src/login.php | 108 +++++++++++++++++++++++++++++++ src/templates/include_menu.html | 9 +++ src/templates/login.html | 67 +++++++++++++++++++ 8 files changed, 296 insertions(+), 73 deletions(-) create mode 100644 src/login.php create mode 100644 src/templates/login.html diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 6c3bb53..3dcc336 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -58,6 +58,7 @@ $CFG['MiscShowDebugGridCounter'] = 0; // Only for debugging purposes, will add $CFG["MiscShowPageRenderStats"] = 1; // If enabled, you will see Pagerender Settings $CFG['MiscEnableGzipCompression'] = 1; // If enabled, phplogcon will use gzip compression for output, we recommend // to have this option enabled, it will highly reduce bandwith usage. +$CFG['DebugUserLogin'] = 0; // if enabled, you will see additional informations on failed logins // --- // --- Default Frontend Options diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 0567f35..64efcff 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -404,8 +404,8 @@ function InitPhpDebugMode() // --- Set Global DEBUG Level! if ( $CFG['MiscShowDebugMsg'] == 1 ) ini_set( "error_reporting", E_ALL ); // ALL PHP MESSAGES! -// else -// ini_set( "error_reporting", E_ERROR ); // ONLY PHP ERROR'S! + else + ini_set( "error_reporting", E_ERROR ); // ONLY PHP ERROR'S! // --- } @@ -520,24 +520,34 @@ function InitConfigurationValues() // If Database is enabled, try to read from database! if ( $CFG['UserDBEnabled'] ) { - $result = DB_Query("SELECT * FROM " . DB_CONFIG); + // Get configuration variables + $result = DB_Query("SELECT * FROM " . DB_CONFIG . " WHERE is_global = true"); $rows = DB_GetAllRows($result, true, true); // Read results from DB and overwrite in $CFG Array! if ( isset($rows ) ) { for($i = 0; $i < count($rows); $i++) - $CFG[ $rows[$i]['name'] ] = $rows[$i]['value']; + { + $CFG[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; + $content[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; + } + } + + // Now we init the user session stuff + InitUserSession(); + + if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true && !$content['SESSION_LOGGEDIN'] ) + { + // User needs to be logged in, redirect to login page + if ( !defined("IS_LOGINPAGE") ) + RedirectToUserLogin(); } // General defaults - // --- Language Handling - if ( !isset($content['gen_lang']) ) { $content['gen_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; } +// // --- Language Handling +// if ( !isset($content['gen_lang']) ) { $content['gen_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; } - // --- PHP Debug Mode - if ( !isset($content['gen_phpdebug']) ) { $content['gen_phpdebug'] = "no"; } - // --- - // Database Version Checker! if ( $content['database_internalversion'] > $content['database_installedversion'] ) { @@ -545,27 +555,25 @@ function InitConfigurationValues() $content['database_forcedatabaseupdate'] = "yes"; } } - else + + // --- Language Handling + if ( isset($_SESSION['CUSTOM_LANG']) && VerifyLanguage($_SESSION['CUSTOM_LANG']) ) { - // --- Set Defaults... - // Language Handling - if ( isset($_SESSION['CUSTOM_LANG']) && VerifyLanguage($_SESSION['CUSTOM_LANG']) ) - { - $content['user_lang'] = $_SESSION['CUSTOM_LANG']; - $LANG = $content['user_lang']; - } - else if ( isset($content['gen_lang']) && VerifyLanguage($content['gen_lang'])) - { - $content['user_lang'] = $content['gen_lang']; - $LANG = $content['user_lang']; - } - else // Failsave! - { - $content['user_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; - $LANG = $content['user_lang']; - $content['gen_lang'] = $content['user_lang']; - } + $content['user_lang'] = $_SESSION['CUSTOM_LANG']; + $LANG = $content['user_lang']; } + else if ( isset($content['gen_lang']) && VerifyLanguage($content['gen_lang'])) + { + $content['user_lang'] = $content['gen_lang']; + $LANG = $content['user_lang']; + } + else // Failsave! + { + $content['user_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; + $LANG = $content['user_lang']; + $content['gen_lang'] = $content['user_lang']; + } + // --- // Paging Size handling! if ( !isset($_SESSION['PAGESIZE_ID']) ) @@ -590,9 +598,8 @@ function InitConfigurationValues() else $content['user_theme'] = $content['web_theme']; - //Init Theme About Info ^^ + // Init Theme About Info ^^ InitThemeAbout($content['user_theme']); - // --- // Init main langauge file now! IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/main.php' ); diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 1af7a73..d7238a6 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -40,7 +40,7 @@ if ( !defined('IN_PHPLOGCON') ) // --- -$link_id = 0; +$userdbconn = 0; $errdesc = ""; $errno = 0; @@ -51,11 +51,11 @@ $content['database_installedversion'] = "0"; // 0 is default which means Prior V function DB_Connect() { - global $link_id, $CFG; + global $userdbconn, $CFG; //TODO: Check variables first - $link_id = mysql_connect($CFG['UserDBServer'],$CFG['UserDBUser'],$CFG['UserDBPass']); - if (!$link_id) + $userdbconn = mysql_connect($CFG['UserDBServer'],$CFG['UserDBUser'],$CFG['UserDBPass']); + if (!$userdbconn) DB_PrintError("Link-ID == false, connect to ".$CFG['UserDBServer']." failed", true); // --- Now, check Mysql DB Version! @@ -78,7 +78,7 @@ function DB_Connect() } // --- - $db_selected = mysql_select_db($CFG['UserDBName'], $link_id); + $db_selected = mysql_select_db($CFG['UserDBName'], $userdbconn); if(!$db_selected) DB_PrintError("Cannot use database '" . $CFG['UserDBName'] . "'", true); // :D Success connecting to db @@ -88,8 +88,8 @@ function DB_Connect() function DB_Disconnect() { - global $link_id; - mysql_close($link_id); + global $userdbconn; + mysql_close($userdbconn); } function DB_Query($query_string, $bProcessError = true, $bCritical = false) @@ -100,8 +100,8 @@ function DB_Query($query_string, $bProcessError = true, $bCritical = false) return; // --- - global $link_id, $querycount; - $query_id = mysql_query($query_string,$link_id); + global $userdbconn, $querycount; + $query_id = mysql_query($query_string,$userdbconn); if (!$query_id && $bProcessError) DB_PrintError("Invalid SQL: ".$query_string, $bCritical); @@ -147,15 +147,12 @@ function DB_GetSingleRow($query_id, $bClose) if ($query_id != false && $query_id != 1 ) { $row = mysql_fetch_array($query_id, MYSQL_ASSOC); - + if ( $bClose ) DB_FreeQuery ($query_id); - if ( isset($row) ) - { - // Return array + if ( isset($row) ) // Return array return $row; - } else return; } @@ -195,8 +192,8 @@ function DB_GetMysqlStats() return; // --- - global $link_id; - $status = explode(' ', mysql_stat($link_id)); + global $userdbconn; + $status = explode(' ', mysql_stat($userdbconn)); return $status; } @@ -282,7 +279,7 @@ function DB_Exec($query) return false; } -function WriteConfigValue($szValue) +function WriteConfigValue($szValue, $is_global = true) { // --- Abort in this case! global $CFG, $content; @@ -290,18 +287,18 @@ function WriteConfigValue($szValue) return; // --- - $result = DB_Query("SELECT name FROM " . STATS_CONFIG . " WHERE name = '" . $szValue . "'"); + $result = DB_Query("SELECT name FROM " . STATS_CONFIG . " WHERE name = '" . $szValue . "' AND is_global = " . $is_global); $rows = DB_GetAllRows($result, true); if ( !isset($rows) ) { // New Entry - $result = DB_Query("INSERT INTO " . STATS_CONFIG . " (name, value) VALUES ( '" . $szValue . "', '" . $CFG[$szValue] . "')"); + $result = DB_Query("INSERT INTO " . STATS_CONFIG . " (name, value, is_global) VALUES ( '" . $szValue . "', '" . $CFG[$szValue] . "', " . $is_global . ")"); DB_FreeQuery($result); } else { // Update Entry - $result = DB_Query("UPDATE " . STATS_CONFIG . " SET value = '" . $CFG[$szValue] . "' WHERE name = '" . $szValue . "'"); + $result = DB_Query("UPDATE " . STATS_CONFIG . " SET value = '" . $CFG[$szValue] . "' WHERE name = '" . $szValue . "' AND is_global = " . $is_global); DB_FreeQuery($result); } } diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 3d97383..9e41182 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -45,39 +45,49 @@ if ( !defined('IN_PHPLOGCON') ) // --- // --- BEGIN Usermanagement Function --- -function CheckForUserLogin( $isloginpage, $isUpgradePage = false ) +function InitUserSession() { global $content; if ( isset($_SESSION['SESSION_LOGGEDIN']) ) { if ( !$_SESSION['SESSION_LOGGEDIN'] ) - RedirectToUserLogin(); + { + $content['SESSION_LOGGEDIN'] = false; + + // Not logged in + return false; + } else { - $content['SESSION_LOGGEDIN'] = "true"; + $content['SESSION_LOGGEDIN'] = true; $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; + + // Successfully logged in + return true; } - +/* // New, Check for database Version and may redirect to updatepage! if ( isset($content['database_forcedatabaseupdate']) && $content['database_forcedatabaseupdate'] == "yes" && $isUpgradePage == false ) RedirectToDatabaseUpgrade(); +*/ } else { - if ( $isloginpage == false ) - RedirectToUserLogin(); - } + $content['SESSION_LOGGEDIN'] = false; + // Not logged in ^^ + return false; + } } -function CreateUserName( $username, $password, $access_level ) +function CreateUserName( $username, $password, $is_admin ) { $md5pass = md5($password); - $result = DB_Query("SELECT username FROM " . STATS_USERS . " WHERE username = '" . $username . "'"); + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $username . "'"); $rows = DB_GetAllRows($result, true); if ( isset($rows) ) { @@ -89,7 +99,7 @@ function CreateUserName( $username, $password, $access_level ) else { // Create User - $result = DB_Query("INSERT INTO " . STATS_USERS . " (username, password, access_level) VALUES ('$username', '$md5pass', $access_level)"); + $result = DB_Query("INSERT INTO " . DB_USERS . " (username, password, is_admin) VALUES ('$username', '$md5pass', $is_admin)"); DB_FreeQuery($result); // Success @@ -104,24 +114,29 @@ function CheckUserLogin( $username, $password ) // TODO: SessionTime and AccessLevel check $md5pass = md5($password); - $sqlselect = "SELECT access_level FROM " . STATS_USERS . " WHERE username = '" . $username . "' and password = '" . $md5pass . "'"; + $sqlselect = "SELECT * FROM " . DB_USERS . " WHERE username = '" . $username . "' and password = '" . $md5pass . "'"; $result = DB_Query($sqlselect); - $rows = DB_GetAllRows($result, true); - if ( isset($rows) ) + $myrow = DB_GetSingleRow($result, true); + + + if ( isset($myrow['is_admin']) ) { $_SESSION['SESSION_LOGGEDIN'] = true; $_SESSION['SESSION_USERNAME'] = $username; - $_SESSION['SESSION_ACCESSLEVEL'] = $rows[0]['access_level']; - - $content['SESSION_LOGGEDIN'] = "true"; - $content['SESSION_USERNAME'] = $username; + $_SESSION['SESSION_ISADMIN'] = $myrow['is_admin']; + + $content['SESSION_LOGGEDIN'] = $_SESSION['SESSION_LOGGEDIN']; + $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; + $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; + + // TODO SET LAST LOGIN TIME! // Success ! return true; } else { - if ( $CFG['MiscShowDebugMsg'] == 1 ) + if ( $CFG['DebugUserLogin'] == 1 ) DieWithFriendlyErrorMsg( "Debug Error: Could not login user '" . $username . "'

Sessionarray
" . var_export($_SESSION, true) . "

SQL Statement: " . $sqlselect ); // Default return false @@ -143,15 +158,23 @@ function DoLogOff() function RedirectToUserLogin() { - // TODO Referer - header("Location: login.php?referer=" . $_SERVER['PHP_SELF']); + // build referer + $referer = $_SERVER['PHP_SELF']; + if ( isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0 ) + $referer .= "?" . $_SERVER['QUERY_STRING']; + + header("Location: login.php?referer=" . urlencode($referer) ); exit; } function RedirectToDatabaseUpgrade() { - // TODO Referer - header("Location: upgrade.php"); // ?referer=" . $_SERVER['PHP_SELF']); + // build referer + $referer = $_SERVER['PHP_SELF']; + if ( isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0 ) + $referer .= "?" . $_SERVER['QUERY_STRING']; + + header("Location: upgrade.php?referer=" . urlencode($referer) ); exit; } // --- END Usermanagement Function --- diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 8c67bb0..00763d3 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -155,4 +155,15 @@ $content['LN_DETAILS_FORSYSLOGMSG'] = "Details for the syslog messages with id"; $content['LN_DETAILS_DETAILSFORMSG'] = "Details for message id"; $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; +// Login Site +$content['LN_LOGIN_DESCRIPTION'] = "Use this form to login into phpLogCon. "; +$content['LN_LOGIN_TITLE'] = "Login"; +$content['LN_LOGIN_USERNAME'] = "Username"; +$content['LN_LOGIN_PASSWORD'] = "Password"; +$content['LN_LOGIN_SAVEASCOOKIE'] = "Stay logged on"; + +$content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; +$content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; + + ?> \ No newline at end of file diff --git a/src/login.php b/src/login.php new file mode 100644 index 0000000..3a33566 --- /dev/null +++ b/src/login.php @@ -0,0 +1,108 @@ + File to login users in PhpLogCon + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +//include($gl_root_path . 'include/functions_filters.php'); + +// To avoid infinite redirects! +define('IS_LOGINPAGE', true); +InitPhpLogCon(); +// --- // + +// --- BEGIN Custom Code + +// Set Defaults +$content['uname'] = ""; +$content['pass'] = ""; + +// Set Referer +if ( isset($_GET['referer']) ) + $szRedir = urldecode($_GET['referer']); +else + $szRedir = "index.php"; // Default + +if ( isset($_POST['op']) && $_POST['op'] == "login" ) +{ + // Perform login! + if ( $_POST['op'] == "login" ) + { + if ( + (isset($_POST['uname']) && strlen($_POST['uname']) > 0) + && + (isset($_POST['pass']) && strlen($_POST['pass']) > 0) + ) + { + // Set Username and password + $content['uname'] = DB_RemoveBadChars($_POST['uname']); + $content['pass'] = DB_RemoveBadChars($_POST['pass']); + + if ( !CheckUserLogin( $content['uname'], $content['pass']) ) + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = $content['LN_LOGIN_ERRWRONGPASSWORD']; + } + else + RedirectPage( $szRedir ); + } + else + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = $content['LN_LOGIN_USERPASSMISSING']; + } + } +} +else if ( isset($_GET['op']) && $_GET['op'] == "logoff" ) +{ + // logoff in this case + DoLogOff(); +} +// --- END Custom Code + +// --- CONTENT Vars +$content['REDIR_LOGIN'] = $szRedir; +$content['TITLE'] = "phpLogCon - User Login"; // Title of the Page +// --- + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "login.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index cc7f009..9d9e717 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -8,6 +8,15 @@ Help Search in Knowledge Base + + + Login + + + Admin Center + Logoff + +   diff --git a/src/templates/login.html b/src/templates/login.html new file mode 100644 index 0000000..db75825 --- /dev/null +++ b/src/templates/login.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + +
+
+

{ERROR_MSG}

+
+
+
+ {LN_LOGIN_DESCRIPTION} +

+ + + + + + + + +
+ {LN_LOGIN_TITLE}
+
+ + + + + + + + + + + + + + + + + +
{LN_LOGIN_USERNAME}
+
{LN_LOGIN_PASSWORD}
+
+ + + +
+
+
+ +

+
+ + \ No newline at end of file From 3ad9adb820e59626680072b3ffd02c186c2c17ea Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 11 Jul 2008 16:50:10 +0200 Subject: [PATCH 004/142] Enhanced installer script to support installation of the user interface The first version of the database layout has also been added. Now the work on the admin center can start --- src/include/db_template.txt | 102 ++++++++++++++++++++++ src/include/functions_db.php | 2 +- src/include/functions_users.php | 1 + src/install.php | 90 +++++++++++++++++--- src/lang/de/main.php | 10 +++ src/lang/en/main.php | 4 +- src/login.php | 1 + src/templates/include_header.html | 8 ++ src/templates/include_menu.html | 6 +- src/templates/install.html | 136 +++++++++++++++++++----------- 10 files changed, 291 insertions(+), 69 deletions(-) create mode 100644 src/include/db_template.txt diff --git a/src/include/db_template.txt b/src/include/db_template.txt new file mode 100644 index 0000000..a432dfd --- /dev/null +++ b/src/include/db_template.txt @@ -0,0 +1,102 @@ +-- MYSQL Table Template +-- for phpLogCon Version 0 +-- http://www.phplogcon.org + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + +-- +-- Table structure for table `logcon_config` +-- +DROP TABLE IF EXISTS `logcon_config`; +CREATE TABLE IF NOT EXISTS `logcon_config` ( + `propname` varchar(32) NOT NULL, + `propvalue` varchar(255) NOT NULL, + `is_global` tinyint(1) NOT NULL, + `userid` int(11) default NULL, + `groupid` int(11) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table to store global and user specific configurations'; + +-- +-- Table structure for table `logcon_groupmembers` +-- +DROP TABLE IF EXISTS `logcon_groupmembers`; +CREATE TABLE IF NOT EXISTS `logcon_groupmembers` ( + `userid` int(11) NOT NULL, + `groupid` int(11) NOT NULL, + `is_member` tinyint(1) NOT NULL default '1' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Helpertable to store which users are in which group'; + +-- +-- Table structure for table `logcon_groups` +-- +DROP TABLE IF EXISTS `logcon_groups`; +CREATE TABLE IF NOT EXISTS `logcon_groups` ( + `ID` int(11) NOT NULL auto_increment, + `groupname` varchar(32) NOT NULL, + `groupdescription` varchar(255) NOT NULL, + `grouptype` int(11) NOT NULL, + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table for phplogcon groups' AUTO_INCREMENT=1 ; + +-- +-- Table structure for table `logcon_searches` +-- +DROP TABLE IF EXISTS `logcon_searches`; +CREATE TABLE IF NOT EXISTS `logcon_searches` ( + `ID` int(11) NOT NULL auto_increment, + `DisplayName` varchar(255) NOT NULL, + `SearchQuery` varchar(1024) NOT NULL, + `userid` int(11) default NULL, + `groupidd` int(11) default NULL, + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Stores custom user searches' AUTO_INCREMENT=1 ; + +-- +-- Table structure for table `logcon_sources` +-- +DROP TABLE IF EXISTS `logcon_sources`; +CREATE TABLE IF NOT EXISTS `logcon_sources` ( + `ID` varchar(64) NOT NULL, + `DisplayName` varchar(255) NOT NULL, + `SourceType` tinyint(4) NOT NULL, + `ViewID` varchar(64) NOT NULL, + `LogLineType` varchar(64) default NULL, + `DiskFile` varchar(255) default NULL, + `DBTableType` varchar(64) default NULL, + `DBType` tinyint(4) default NULL, + `DBServer` varchar(255) default NULL, + `DBName` varchar(64) default NULL, + `DBUser` varchar(64) default NULL, + `DBPassword` varchar(255) default NULL, + `DBTableName` varchar(64) default NULL, + `DBEnableRowCounting` tinyint(1) default NULL, + `userid` int(11) default NULL, + `groupid` int(11) default NULL, + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table to store datasources in phplogcon'; + +-- +-- Table structure for table `logcon_users` +-- +DROP TABLE IF EXISTS `logcon_users`; +CREATE TABLE IF NOT EXISTS `logcon_users` ( + `ID` int(11) NOT NULL auto_increment, + `username` varchar(32) NOT NULL, + `password` varchar(32) NOT NULL, + `is_admin` tinyint(1) NOT NULL default '0', + `last_login` tinyint(4) NOT NULL, + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table for the phplogcon users' AUTO_INCREMENT=2 ; + +-- +-- Table structure for table `logcon_views` +-- +DROP TABLE IF EXISTS `logcon_views`; +CREATE TABLE IF NOT EXISTS `logcon_views` ( + `ID` varchar(64) NOT NULL, + `DisplayName` varchar(255) NOT NULL, + `Columns` text NOT NULL, + `userid` int(11) default NULL, + `groupid` int(11) default NULL, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Stores custom defined user views.'; diff --git a/src/include/functions_db.php b/src/include/functions_db.php index d7238a6..a0f52e0 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -45,7 +45,7 @@ $errdesc = ""; $errno = 0; // --- Current Database Version, this is important for automated database Updates! -$content['database_internalversion'] = "0"; // Whenever incremented, a database upgrade is needed +$content['database_internalversion'] = "1"; // Whenever incremented, a database upgrade is needed $content['database_installedversion'] = "0"; // 0 is default which means Prior Versioning Database // --- diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 9e41182..6634153 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -89,6 +89,7 @@ function CreateUserName( $username, $password, $is_admin ) $md5pass = md5($password); $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $username . "'"); $rows = DB_GetAllRows($result, true); + if ( isset($rows) ) { DieWithFriendlyErrorMsg( "User $username already exists!" ); diff --git a/src/install.php b/src/install.php index 1fbc98b..071bdfb 100644 --- a/src/install.php +++ b/src/install.php @@ -212,6 +212,18 @@ else if ( $content['INSTALL_STEP'] == 3 ) $content['UserDBEnabled_true'] = ""; $content['UserDBEnabled_false'] = "checked"; } + if ( $content['UserDBLoginRequired'] == 1 ) + { + $content['UserDBLoginRequired_true'] = "checked"; + $content['UserDBLoginRequired_false'] = ""; + } + else + { + $content['UserDBLoginRequired_true'] = ""; + $content['UserDBLoginRequired_false'] = "checked"; + } + + // --- // --- Read and predefine Frontend options @@ -290,6 +302,12 @@ else if ( $content['INSTALL_STEP'] == 4 ) else $_SESSION['UserDBPass'] = ""; + if ( isset($_POST['UserDBLoginRequired']) ) + $_SESSION['UserDBLoginRequired'] = intval(DB_RemoveBadChars($_POST['UserDBLoginRequired'])); + else + $_SESSION['UserDBLoginRequired'] = false; + + // Now Check database connect $link_id = mysql_connect( $_SESSION['UserDBServer'], $_SESSION['UserDBUser'], $_SESSION['UserDBPass']); if (!$link_id) @@ -350,12 +368,12 @@ else if ( $content['INSTALL_STEP'] == 5 ) $totaldbdefs = ""; // Read the table GLOBAL definitions - ImportDataFile( $content['BASEPATH'] . "contrib/db_template.txt" ); + ImportDataFile( $content['BASEPATH'] . "include/db_template.txt" ); // Process definitions ^^ if ( strlen($totaldbdefs) <= 0 ) { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = "Error, invalid Database Defintion File (to short!), file '" . $content['BASEPATH'] . "contrib/db_template.txt" . "'!
Maybe the file was not correctly uploaded?"; + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = "Error, invalid Database Defintion File (to short!), file '" . $content['BASEPATH'] . "include/db_template.txt" . "'!
Maybe the file was not correctly uploaded?"; $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; $content['sql_failed']++; } @@ -366,24 +384,24 @@ else if ( $content['INSTALL_STEP'] == 5 ) // Now split by sql command $mycommands = split( ";\n", $totaldbdefs ); - // check for different linefeed - if ( count($mycommands) <= 1 ) - $mycommands = split( ";\n", $totaldbdefs ); +// // check for different linefeed +// if ( count($mycommands) <= 1 ) +// $mycommands = split( ";\n", $totaldbdefs ); //Still only one? Abort if ( count($mycommands) <= 1 ) { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = "Error, invalid Database Defintion File (no statements found!) in '" . $content['BASEPATH'] . "contrib/db_template.txt" . "'!
Maybe the file was not correctly uploaded, or a strange bug with your system? Contact phpLogCon forums for assistance!"; + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = "Error, invalid Database Defintion File (no statements found!) in '" . $content['BASEPATH'] . "include/db_template.txt" . "'!
Maybe the file was not correctly uploaded, or a strange bug with your system? Contact phpLogCon forums for assistance!"; $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; $content['sql_failed']++; } // Append INSERT Statement for Config Table to set the GameVersion and Database Version ^^! - $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`name`, `value`) VALUES ('database_installedversion', '1')"; + $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '1', 1)"; // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! - InitPhpLogConConfigFile(); + InitUserDbSettings(); // Establish DB Connection DB_Connect(); @@ -460,20 +478,25 @@ else if ( $content['INSTALL_STEP'] == 7 ) $_SESSION['MAIN_Password2'] = ""; if ( - strlen($_SESSION['MAIN_Password1']) <= 4 || + strlen($_SESSION['MAIN_Password1']) < 4 || $_SESSION['MAIN_Password1'] != $_SESSION['MAIN_Password2'] ) RevertOneStep( $content['INSTALL_STEP']-1, "Either the password does not match or is to short!" ); // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! - InitPhpLogConConfigFile(); + InitUserDbSettings(); // We need some DB Settings + InitUserSystemPhpLogCon(); // We need the user system now! // Establish DB Connection DB_Connect(); // Everything is fine, lets go create the User! - CreateUserName( $_SESSION['MAIN_Username'], $_SESSION['MAIN_Password1'], 0 ); + CreateUserName( $_SESSION['MAIN_Username'], $_SESSION['MAIN_Password1'], 1 ); + + // Show User success! + $content['MAIN_Username'] = $_SESSION['MAIN_Username']; + $content['createduser'] = true; } // Init Source Options @@ -491,7 +514,6 @@ else if ( $content['INSTALL_STEP'] == 7 ) $content['Views'][ $myView['ID'] ]['selected'] = ""; } - // SOURCE_DISK specific if ( isset($_SESSION['SourceLogLineType']) ) { $content['SourceLogLineType'] = $_SESSION['SourceLogLineType']; } else { $content['SourceLogLineType'] = ""; } CreateLogLineTypesList($content['SourceLogLineType']); @@ -611,17 +633,35 @@ else if ( $content['INSTALL_STEP'] == 8 ) // If we reached this point, we have gathered all necessary information to create our configuration file ;)! $filebuffer = LoadDataFile($configsamplefile); + // helper variables + if ( $_SESSION['UserDBEnabled'] ) { $_SESSION['UserDBEnabled_value'] = "true"; } else { $_SESSION['UserDBEnabled_value'] = "false"; } + if ( $_SESSION['UserDBLoginRequired'] ) { $_SESSION['UserDBLoginRequired_value'] = "true"; } else { $_SESSION['UserDBLoginRequired_value'] = "false"; } + // Start replacing existing sample configurations $patterns[] = "/\\\$CFG\['ViewMessageCharacterLimit'\] = [0-9]{1,2};/"; $patterns[] = "/\\\$CFG\['ViewEntriesPerPage'\] = [0-9]{1,2};/"; $patterns[] = "/\\\$CFG\['ViewEnableDetailPopups'\] = [0-9]{1,2};/"; $patterns[] = "/\\\$CFG\['EnableIPAddressResolve'\] = [0-9]{1,2};/"; - $patterns[] = "/\\\$CFG\['UserDBEnabled'\] = [0-9]{1,2};/"; + $patterns[] = "/\\\$CFG\['UserDBEnabled'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBServer'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBPort'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBName'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBPref'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBUser'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBPass'\] = (.*?);/"; + $patterns[] = "/\\\$CFG\['UserDBLoginRequired'\] = (.*?);/"; $replacements[] = "\$CFG['ViewMessageCharacterLimit'] = " . $_SESSION['ViewMessageCharacterLimit'] . ";"; $replacements[] = "\$CFG['ViewEntriesPerPage'] = " . $_SESSION['ViewEntriesPerPage'] . ";"; $replacements[] = "\$CFG['ViewEnableDetailPopups'] = " . $_SESSION['ViewEnableDetailPopups'] . ";"; $replacements[] = "\$CFG['EnableIPAddressResolve'] = " . $_SESSION['EnableIPAddressResolve'] . ";"; - $replacements[] = "\$CFG['UserDBEnabled'] = " . $_SESSION['UserDBEnabled'] . ";"; + $replacements[] = "\$CFG['UserDBEnabled'] = " . $_SESSION['UserDBEnabled_value'] . ";"; + $replacements[] = "\$CFG['UserDBServer'] = '" . $_SESSION['UserDBServer'] . "';"; + $replacements[] = "\$CFG['UserDBPort'] = " . $_SESSION['UserDBPort'] . ";"; + $replacements[] = "\$CFG['UserDBName'] = '" . $_SESSION['UserDBName'] . "';"; + $replacements[] = "\$CFG['UserDBPref'] = '" . $_SESSION['UserDBPref'] . "';"; + $replacements[] = "\$CFG['UserDBUser'] = '" . $_SESSION['UserDBUser'] . "';"; + $replacements[] = "\$CFG['UserDBPass'] = '" . $_SESSION['UserDBPass'] . "';"; + $replacements[] = "\$CFG['UserDBLoginRequired'] = " . $_SESSION['UserDBLoginRequired_value'] . ";"; //User Database Options if ( $_SESSION['UserDBEnabled'] == 1 ) @@ -759,5 +799,27 @@ function ImportDataFile($szFileName) } } +function InitUserDbSettings() +{ + global $CFG; + + // Init DB Configs + $CFG['UserDBEnabled'] = true; + $CFG['UserDBServer'] = $_SESSION['UserDBServer']; + $CFG['UserDBPort'] = $_SESSION['UserDBPort']; + $CFG['UserDBName'] = $_SESSION['UserDBName']; + $CFG['UserDBPref'] = $_SESSION['UserDBPref']; + $CFG['UserDBUser'] = $_SESSION['UserDBUser']; + $CFG['UserDBPass'] = $_SESSION['UserDBPass']; + $CFG['UserDBLoginRequired'] = $_SESSION['UserDBLoginRequired']; + + // Needed table defs + define('DB_CONFIG', $CFG['UserDBPref'] . "config"); + define('DB_USERS', $CFG['UserDBPref'] . "users"); + define('DB_SEARCHES', $CFG['UserDBPref'] . "searches"); + define('DB_SOURCES', $CFG['UserDBPref'] . "sources"); + define('DB_VIEWS', $CFG['UserDBPref'] . "views"); +} + // --- ?> \ No newline at end of file diff --git a/src/lang/de/main.php b/src/lang/de/main.php index afded35..f0abdf9 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -146,10 +146,20 @@ $content['LN_CFG_DBTABLENAME'] = "Datenbank Tabellenname"; $content['LN_CFG_NAMEOFTHESOURCE'] = "Name der Quelle"; $content['LN_CFG_FIRSTSYSLOGSOURCE'] = "Erste Syslog Quelle"; $content['LN_CFG_VIEW'] = "Select View"; + $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Details für syslog-Nachrichten mit der ID"; $content['LN_DETAILS_DETAILSFORMSG'] = "Details für Nachrichten-ID"; $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; + // Login Site + $content['LN_LOGIN_DESCRIPTION'] = "Use this form to login into phpLogCon. "; + $content['LN_LOGIN_TITLE'] = "Login"; + $content['LN_LOGIN_USERNAME'] = "Username"; + $content['LN_LOGIN_PASSWORD'] = "Password"; + $content['LN_LOGIN_SAVEASCOOKIE'] = "Stay logged on"; + $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; + $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; + ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 00763d3..baedc13 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -148,7 +148,8 @@ $content['LN_CFG_DBTABLENAME'] = "Database Tablename"; $content['LN_CFG_NAMEOFTHESOURCE'] = "Name of the Source"; $content['LN_CFG_FIRSTSYSLOGSOURCE'] = "First Syslog Source"; $content['LN_CFG_DBROWCOUNTING'] = "Enable Row Counting"; - $content['LN_CFG_VIEW'] = "Select View"; +$content['LN_CFG_VIEW'] = "Select View"; +$content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Details for the syslog messages with id"; @@ -161,7 +162,6 @@ $content['LN_LOGIN_TITLE'] = "Login"; $content['LN_LOGIN_USERNAME'] = "Username"; $content['LN_LOGIN_PASSWORD'] = "Password"; $content['LN_LOGIN_SAVEASCOOKIE'] = "Stay logged on"; - $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; diff --git a/src/login.php b/src/login.php index 3a33566..4b0da04 100644 --- a/src/login.php +++ b/src/login.php @@ -42,6 +42,7 @@ include($gl_root_path . 'include/functions_frontendhelpers.php'); // To avoid infinite redirects! define('IS_LOGINPAGE', true); +$content['IS_LOGINPAGE'] = true; InitPhpLogCon(); // --- // diff --git a/src/templates/include_header.html b/src/templates/include_header.html index 5517866..a4314ae 100644 --- a/src/templates/include_header.html +++ b/src/templates/include_header.html @@ -23,6 +23,7 @@ +
@@ -38,6 +39,7 @@
+   @@ -45,6 +47,7 @@ +
@@ -60,12 +63,14 @@
+ +
@@ -80,12 +85,14 @@
+ +
@@ -101,6 +108,7 @@
+ diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index 9d9e717..4fbb083 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -10,11 +10,11 @@ Search in Knowledge Base - Login + Login - Admin Center - Logoff + Admin Center + Logoff   diff --git a/src/templates/install.html b/src/templates/install.html index 319d70d..6673f67 100644 --- a/src/templates/install.html +++ b/src/templates/install.html @@ -133,49 +133,14 @@

 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -185,17 +150,74 @@ - + + + + + + + +
User Database Options
Enable User Database - Yes No -
{LN_CFG_DBSERVER}
{LN_CFG_DBPORT}
{LN_CFG_DBNAME}
{LN_CFG_DBPREF}
{LN_CFG_DBUSER}
{LN_CFG_DBPASSWORD}
Frontend Options
Number of syslog messages per pageNumber of syslog messages per page
Message character limit for the main view
Show message details popup
Automatically resolved IP Addresses (inline) + Yes No
 
User Database Options
Enable User Database + Yes + No +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_CFG_DBSERVER}
{LN_CFG_DBPORT}
{LN_CFG_DBNAME}
{LN_CFG_DBPREF}
{LN_CFG_DBUSER}
{LN_CFG_DBPASSWORD}
{LN_CFG_DBUSERLOGINREQUIRED} + Yes + No +
+
+

 

 

+ + @@ -203,11 +225,12 @@

Step 4 - Create Tables

-

If you reached this step, the database configuration file has been successfully written!
- The next step will start creating the database tables.
- This might take a while, so be patient!

+

If you reached this step, the database connection has been successfully verified!

+ The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
+ WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database. +

+ Click on to start the creation of the tables - Go ahead when you feel ready ;)

 

 

@@ -260,20 +283,22 @@

Step 6 - Creating the Main Useraccount

You are now about to create the initial phpLogCon User Account.
- This useraccount is important, as it will allow you to login into the Admin center! + This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center! +

+ - - + + - + - - + +
Create User Account
UsernameUsername
PasswordPassword
Repeat PasswordRepeat Password
@@ -285,6 +310,19 @@ + + + + + + +
+
+

Successfully created User '{MAIN_Username}'.

+
+
+ + - + @@ -28,13 +28,13 @@ - +
From 48a6ef124e58029b2937f93f53d775ded7c0584e Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 14 Jul 2008 11:30:09 +0200 Subject: [PATCH 005/142] Added icons into header menu and added menu entries into language system Done minor other adjustments in the default css of the dark and default style --- src/include/functions_common.php | 3 ++- src/lang/de/main.php | 10 ++++++++++ src/lang/en/main.php | 9 +++++++++ src/lang/pt_BR/main.php | 21 ++++++++++++++++++++- src/templates/include_footer.html | 6 +++--- src/templates/include_menu.html | 18 +++++++++++------- src/themes/dark/main.css | 2 ++ src/themes/default/main.css | 2 ++ 8 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 64efcff..10acfa6 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -472,7 +472,8 @@ function InitFrontEndVariables() $content['MENU_SELECTION_ENABLED'] = $content['BASEPATH'] . "images/icons/selection_delete.png"; $content['MENU_TEXT_FIND'] = $content['BASEPATH'] . "images/icons/text_find.png"; $content['MENU_NETWORK'] = $content['BASEPATH'] . "images/icons/earth_network.png"; - + $content['MENU_HELP'] = $content['BASEPATH'] . "images/icons/help.png"; + $content['MENU_KB'] = $content['BASEPATH'] . "images/icons/books.png"; $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; diff --git a/src/lang/de/main.php b/src/lang/de/main.php index f0abdf9..1f9ed04 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -57,6 +57,16 @@ $content['LN_GEN_SOURCE_DB'] = "Datenbank"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; +// Topmenu Entries +$content['LN_MENU_SEARCH'] = "Suchen"; + $content['LN_MENU_SHOWEVENTS'] = "Show Events"; +$content['LN_MENU_HELP'] = "Hilfe"; + $content['LN_MENU_SEARCHINKB'] = "Search in Knowledge Base"; +$content['LN_MENU_LOGIN'] = "Login"; + $content['LN_MENU_ADMINCENTER'] = "Admin Center"; +$content['LN_MENU_LOGOFF'] = "Logoff"; + $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; + // Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Warnung! Du hast das Installationsscript 'install.php' noch nicht aus dem phpLogCon Hauptordner entfernt!"; $content['LN_TOP_NUM'] = "No."; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index baedc13..8ce2d2d 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -58,6 +58,15 @@ $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; +// Topmenu Entries + $content['LN_MENU_SEARCH'] = "Search"; + $content['LN_MENU_SHOWEVENTS'] = "Show Events"; + $content['LN_MENU_HELP'] = "Help"; + $content['LN_MENU_SEARCHINKB'] = "Search in Knowledge Base"; + $content['LN_MENU_LOGIN'] = "Login"; + $content['LN_MENU_ADMINCENTER'] = "Admin Center"; + $content['LN_MENU_LOGOFF'] = "Logoff"; + $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Warning! You still have NOT removed the 'install.php' from your phpLogCon main directory!"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 1039ab7..d241e4c 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -61,6 +61,16 @@ $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Visão"; +// Topmenu Entries + $content['LN_MENU_SEARCH'] = "Search"; + $content['LN_MENU_SHOWEVENTS'] = "Show Events"; + $content['LN_MENU_HELP'] = "Help"; + $content['LN_MENU_SEARCHINKB'] = "Search in Knowledge Base"; + $content['LN_MENU_LOGIN'] = "Login"; + $content['LN_MENU_ADMINCENTER'] = "Admin Center"; + $content['LN_MENU_LOGOFF'] = "Logoff"; + $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; + // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Atenção! Você ainda NÃO removeu o arquivo 'install.php' do diretório de seu phpLogCon!"; $content['LN_TOP_NUM'] = "Não."; @@ -157,4 +167,13 @@ $content['LN_DETAILS_FORSYSLOGMSG'] = "Detalhes para a mensagem com id"; $content['LN_DETAILS_DETAILSFORMSG'] = "Detalhes para a mensagem com id"; $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; -?> + // Login Site + $content['LN_LOGIN_DESCRIPTION'] = "Use this form to login into phpLogCon. "; + $content['LN_LOGIN_TITLE'] = "Login"; + $content['LN_LOGIN_USERNAME'] = "Username"; + $content['LN_LOGIN_PASSWORD'] = "Password"; + $content['LN_LOGIN_SAVEASCOOKIE'] = "Stay logged on"; + $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; + $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; + +?> \ No newline at end of file diff --git a/src/templates/include_footer.html b/src/templates/include_footer.html index 2d21a4f..1723563 100644 --- a/src/templates/include_footer.html +++ b/src/templates/include_footer.html @@ -2,7 +2,7 @@ - + @@ -10,12 +10,12 @@  Partners: diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index 4fbb083..4fbb177 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -1,22 +1,26 @@
Created 2008 - By Adiscon GmbHMade by Adiscon GmbH (2008)  phpLogCon Version {BUILDNUMBER} -  rsyslog | +  Rsyslog |  WinSyslog - Page rendered in {PAGERENDERTIME} seconds + Page rendered: {PAGERENDERTIME} seconds  | DB queries: {TOTALQUERIES}  | GZIP enabled: {GzipCompressionEnmabled}
- - + + - - + + - + + - - + + + + +
SearchShow Events{LN_MENU_SEARCH}{LN_MENU_SHOWEVENTS} HelpSearch in Knowledge Base{LN_MENU_HELP}{LN_MENU_SEARCHINKB} Login{LN_MENU_LOGIN}  Admin CenterLogoff{LN_MENU_ADMINCENTER}{LN_MENU_LOGOFF}{LN_MENU_LOGGEDINAS} "{SESSION_USERNAME}"  
diff --git a/src/themes/dark/main.css b/src/themes/dark/main.css index 2e39f63..7f7f05b 100644 --- a/src/themes/dark/main.css +++ b/src/themes/dark/main.css @@ -196,6 +196,8 @@ font height: 20px; border:1px ridge; border-color: #D79993 #290604 #290604 #D79993; + padding: 2px 2px 0px 2px; + vertical-align: middle; font: 10px Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; diff --git a/src/themes/default/main.css b/src/themes/default/main.css index d7bbf4b..c38750d 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -199,6 +199,8 @@ font height: 20px; border:1px ridge; border-color: #79AABE #09506C #79AABE #79AABE; + padding: 2px 2px 0px 2px; + vertical-align: middle; font: 10px Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; From b15cfe9792fe461f7bdfdb7a8022f5ca8e0cad5d Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 14 Jul 2008 16:35:35 +0200 Subject: [PATCH 006/142] Added Admin Index and Menu, and made necessary changes to the other templates Added Icons, also into the menu --- src/admin/index.php | 125 +++++++++++++++++++++++ src/images/icons/books.png | Bin 0 -> 1005 bytes src/images/icons/businessman.png | Bin 0 -> 824 bytes src/images/icons/businessmen.png | Bin 875 -> 875 bytes src/images/icons/data_edit.png | Bin 0 -> 928 bytes src/images/icons/document_view.png | Bin 0 -> 759 bytes src/images/icons/googleicon.png | Bin 0 -> 1183 bytes src/images/icons/help.png | Bin 0 -> 1013 bytes src/include/functions_common.php | 5 +- src/include/functions_users.php | 1 + src/templates/admin/admin_index.html | 11 +++ src/templates/admin/admin_menu.html | 18 ++++ src/templates/include_footer.html | 2 +- src/templates/include_header.html | 142 +++++++++++++++------------ 14 files changed, 237 insertions(+), 67 deletions(-) create mode 100644 src/admin/index.php create mode 100644 src/images/icons/books.png create mode 100644 src/images/icons/businessman.png create mode 100644 src/images/icons/data_edit.png create mode 100644 src/images/icons/document_view.png create mode 100644 src/images/icons/googleicon.png create mode 100644 src/images/icons/help.png create mode 100644 src/templates/admin/admin_index.html create mode 100644 src/templates/admin/admin_menu.html diff --git a/src/admin/index.php b/src/admin/index.php new file mode 100644 index 0000000..11b01f6 --- /dev/null +++ b/src/admin/index.php @@ -0,0 +1,125 @@ + Shows ... + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +// include($gl_root_path . 'classes/logstream.class.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); + +// --- Define Extra Stylesheet! +//$content['EXTRA_STYLESHEET'] = '' . "\r\n"; +//$content['EXTRA_STYLESHEET'] .= ''; +// --- + +// --- CONTENT Vars +/* +if ( isset($_GET['uid']) ) + $content['uid_current'] = intval($_GET['uid']); +else + $content['uid_current'] = UID_UNKNOWN; + +// Copy UID for later use ... +$content['uid_fromgetrequest'] = $content['uid_current']; + +// Init Pager variables +$content['uid_first'] = UID_UNKNOWN; +$content['uid_last'] = UID_UNKNOWN; +$content['main_pagerenabled'] = false; +$content['main_pager_first_found'] = false; +$content['main_pager_previous_found'] = false; +$content['main_pager_next_found'] = false; +$content['main_pager_last_found'] = false; + +// Set Default reading direction +$content['read_direction'] = EnumReadDirection::Backward; + +// If set read direction property! +if ( isset($_GET['direction']) ) +{ + if ( $_GET['direction'] == "next" ) + { + $content['skiprecords'] = 1; + $content['read_direction'] = EnumReadDirection::Backward; + } + else if ( $_GET['direction'] == "previous" ) + { + $content['skiprecords'] = 1; + $content['read_direction'] = EnumReadDirection::Forward; + } +} +*/ + +/* +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); + +if ( $content['messageenabled'] == "true" ) +{ + // Append custom title part! + $content['TITLE'] .= " :: Details for '" . $content['uid_current'] . "'"; +} +else +{ + // APpend to title Page title + $content['TITLE'] .= " :: Unknown uid"; +} +// --- END CREATE TITLE +*/ + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_index.html"); +$page -> output(); +// --- + + +?> \ No newline at end of file diff --git a/src/images/icons/books.png b/src/images/icons/books.png new file mode 100644 index 0000000000000000000000000000000000000000..dd1a4919cd5822ac3cda50c4779a4d84042aa77d GIT binary patch literal 1005 zcmVWdKuQAUPmLWnv&QFfcM8F*Z6dF*-FdAS*C2FfftZS~&m!00(qQO+^RR z1s4r5C5RmLVgLXD32;bRa{vGe@Bjb`@Bu=sG?)MY00d`2O+f$vv5yP-)A{yLPs8?bt`beH0eaAdH}*iIPl>0Sy7;6<%o6D}&Lf zH>fuzUg(8L)NGm{Hzu0+fhbxXi%T342F*5V_rchvYiqyU`nmR9eX5;VJjwsLcyj*# z^PK0Lzr?>Gh2F+HpCzzfq}S3&m!{FJsdQyBEDU|mvuA%`M&uO*s%#1+^)>I8tIbC> zYst1Ho0W_fP2Aoh(2AsWAf-QVQ%$AwO-4?fI?uO)vnsH@As`>w$Lp=_m5y}DFBy$F z)|}6QWj~U68?tObZgA41Mr_?}JlxhuF&N@E!7FWu$hH7I{f+DWx>BH`TTt>pQOyL= z6T=kZW8{(%*2-YDqI7j~U%-uLEW&ucL{9K^dFtiXGwOnF8EDC|m{u`0RFSIXe;PFy zLc?id}Fcd0H- z5uS;1HVIQ}U~;)B6upx-MpzK!CV>Wpx#5XY|5Fc*41dPq$FFnljyYVZITk8T4B8HM zH6P>9j=OoSWhX5TCnMK_`J?)=v>-3n_CmsQa4UOX_)&fAf$jQLu{l@TdE)kG=;_^t z%V`t8loHiLgff>IPfifJDMm83beeHa3xTjg^g@g)zUPY$@2Bd$D_U|%<%{AV?_6Ib z9u>(64`_Y#Iq&4a)~DGkU%K$&_#1h#khKt0z;rS0uZ6n)|hv z*t17v_jZ|PhZ&n)B_-QAb#8Y6i~kb2*;*TAX~PQwZu;K)V89oASGcEBvvQYo}91ypip;a<^KXwUBp@YpnB2<%A2hG bpQZl>rT0E@$`60A00000NkvXXu0mjfnN84C literal 0 HcmV?d00001 diff --git a/src/images/icons/businessman.png b/src/images/icons/businessman.png new file mode 100644 index 0000000000000000000000000000000000000000..efbc1af7e61c6c549d22dec0b1d0f59ffb85902a GIT binary patch literal 824 zcmV-81IPS{P)WdKBPATcx`PH%P~GB7YQATc&NG&4FdHXti7F)%RO1yJn(000McNliru z)&&<0F)A3N=<@&o010qNS#tmY3lRVS3lRZ-WM7d0000DMK}|sb0I`n?{9y$E00NLn zL_t(|+I>?^PZLoTJ=0Fh&`wKfX{$g>5l|s2L?Fh5K+?p8M%*BAVH6i`{8$+i5~C~s zfF{byogazfMv^8bCc1GW5;QjQ4H1cjwoZrAnNDHaj(2cKC?YR8$(woio_Fp$_cGYA zUR*~X>%;>Sus@T@8WUR%R?NU z#TvIaAV`7^m~v(qI(j=;}8PXO=xL9NopMMlm!5n6Rw3h8y%PLEcqV-n-y=vJ>f7i zDF)d!s)622&Ee;gsNZVBF016{`Gu3%-@n$rQxy3XJ!xLDROc13Fa8?GD^$p$NYseyawo2I~ zaQOL7{ifAi9>INfux1SrHH23ifDjka+U$m`6kxXG&EWlYl;Ykhf73>jR`jJL6)OoD z#~a^)Z}m2`)N(GL z*UIzun4oA*HkVmjURY74KmNi@Bm+hGK^Y^2X~M>qC1r$KLL;HZsL@Wa8pE?rpw2>! zFiV&>`a;|8DQie8Hxezz8|ef^{WfD7Wn;fNm45&Y8sl>0o|CEo0000WdKcSAT=OOa(W;#FfcP9F*iCeF*-0cAS*C2Fffdk6>0zg00(qQO+^RR z1s4r5HI=L71^@s632;bRa{vGe@Bjb`@Bu=sG?)MY00d`2O+f$vv5yPh|r@0YTv7zVAzpQ-f(%tCh<7N}(Xn=JPYdiNvFKBscj#4LUnpUSGU;ym5V< zzM6n#)!;ZjT-R?eQAQ!|-P zGZu6n$>cP?eM^pNTE0MW$<01+i9+sYGb?4h_SvdE{vPcvski4%oPj8`}b}qElbUiZjAUq ztZp^fxGmIRRHvMep#@EQKarN-eUWdKcYATc!{L3L*!GB7YTATcpIGch_bG$1Q5F)%RM{uz1z000McNliru z)&&<0F*%$asVx8i010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00K`* zL_t(|+Kp06XcJKsJ#U)W{F-XiN+~K@Y%vSdMOzoeg+)k-Qc$uhD7es_WZ}kz6$HhF zh&ziGe_&N6RVq~?rCO@pxe!ZHQw0|;L?vxvGQZ5n`!X}_^n-Ze%zba}z31H@1Id-4 zoW*sus&fU+)taqUt%@Jtznp$_^Snsc62X91IKjUpbMc`ycx}w;J#z9Kxi=H)+*lvh z5sCp%$uRA6G!n!>`Vco=HZ7A)J->U0&Of<}Kp)1Pyuug*BP}p~26pe*iU-*Vl*$r1 z_9riqsgn?0%0N}M6$IIA7PE67A*(8a^&1e6$B{YJPXscq-+01b>mtk%7VHu%b2(TP z(cICAWIBz{3NXJ26yN5M8y`bo|1lVb$(+~y++SE-`-e|?wC&jigWi!P4cZ?WMxqa2 z=kv9`*xjjBSkpc`(7u_ZB}vBY0-ZG#g^G@{q(k=yQIVyk=6c2Foxe^Fp+G(IM8Fgc ziejKt70?p2P!5M-7rG(?y9Cp;O**Z|-u5==@2BuvZ$PE45lzts6efld2!+9352Jfu z>)H$yX9@;oaQJ8%&qhW38Xbfrt58BgG;WL`)vaUmn-L^Jy=w{FJ<;kuq(TeuSLA(@Wc=7b{EBe(!2)W(dNk|j6uilMygr`{{ pydzAz1OdV}NB@uTxfcnGe*q#a91@_~_G175002ovPDHLkV1h7$LCF9B literal 0 HcmV?d00001 diff --git a/src/images/icons/googleicon.png b/src/images/icons/googleicon.png new file mode 100644 index 0000000000000000000000000000000000000000..04c72ea6472ff14d1dc6b9e0dc19f8544b3a1efa GIT binary patch literal 1183 zcmds#i%(N`6vwY_iGWZLC=3PzQ4rX86o$y$GGQ=^JnSfs4a_VrLx%DY+|0IY(`;tU z%{pBqF3VWdWCAMffP#yQLB|+nyo$CGWE6Rnhoz;Ddv9-h``zW=*e^N1ll*e>{hrS` zzh+ThriWXg8$l2rS;F*uE_-c;%X8cv8RID?h^=L!oPvyP70X46gF;2hp<9KyT^Von zi3+K9A$h>nfr+a@)?}50|$0V}`z;30Sm|ug_4lIzEn|)>ij7HkV z0wloD*s%Q*8^%9O(!e^PG2yeHS4yfocfTeNO!({hy>fx1ukNPd-oHi*%%GP>BM5%Q zm=m-fWWN<}Hz|D*f1g>y3g7^ifgufo_I(=_-?YxjnK9wDE%dOm>Z`}QlCG@Uz&H(X zur%lHz{nG`QuUDHifm?8q-Suo&T7Z$E3^LlFHKI_D1gS25fqI%3LBtcXJEw!iuMtWk58q8>mY5LYv8oV`=d@lvYa2?9-gQ; zD;;;RlxlZCND-x@5gI$->D_u)vq|aPM9|yp7+(ZG{wEpEIU7%ugBtwk?q4L`ZOQsTwAb49iU!Bsm=(_#}|fKTQqb{@id=ex8`%+W4QGfzjGY4$HM( z_+BGH@E&Z&mchCkl?1WHBP%_%;M7kQ5=mwG(Xy%vop#~r(%j-grFP!m%h%g)2j8dV z!VecOe_wUsQcJO@AiwCHLa}E2p-Npg{%A5e{?&xNf~3S=S@&;>G8zA3Z`Vg&Cz?)t z*l@CuGFBQ*EiCjb3N(0T`Kw7BK_uk^LOVC33I%gv-7g5gxO<%yN@Y(4Glv> zBg1i#m66f0Q86z|J6t9@ZnpC}+p2DK)Kwq*pms;~@fySOGdIK0zbv8l(P~nS=GLrCerj?lCap9;aeP)@p}Rfv z3AsM~xmLUL^aJWdKHUATcr^L}hv)GB7YRATlvJG%`9cG9W83F)%QmgE8*_000McNliru z)&&<0G8P=+$F~3g010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00T`) zL_t(|+Fg@-Xj69-$G`XHCbwzRG@@i#(}$*AgI%%HF*Zb)R0h6a?2j^773tJMVD9zD z24mYl1Kl6O=wBOSSeP)zhGUzLVzCI;bZc6T5v#Q}jm@L6Hzujgk8`yXo1{x!o>frSSlj;Y}=Ti(pQ2>RLBq`MDYk%SF)TgL4 zEn}x^F9rkOz+2a{o?=BU;rqEW7@q$YZ#>6p2LA^gGUSHty}ps)$MM zPjH$OCjw`{GfsSQ{TNPOJO~HN<5c(8aKnRhxnTra0`U9&uaLNlXy6=nzALf%5LKoE zr|iPcOo#;g#<57Dz=bq=x8J#dU29*?J$#O><=Bw1qZu6l^7N*B^$ zG?*}X1H*k6z?cjM{@RZf?FsZc6S@Itnr6X}coX5^ayemPdu!h=L$jj{88()M&X{=T zkJl*^ZCz;8164ypuV)8@+!G{|$?WqsvZ+*RtRz0e{<BFK1BFd7>Pt;iL}qGF0iq?v2&5wzr`Wn=V;@)k(C9k$r8GM+JdehH&YiR zWaK>F>o|($_%=*VhcoGPS|o9jsM=z&Sf#DKt)Lp}f%gL+vAolXJJK|=dI4)T5nhiE zUv-|w?nEDcyYj1adGyjHQ4|v-zDZ=QVyqXWzrTO5cSrA+FL(5KI$K^snJZD{sc35^ zCT4D6CN{k?etrBZjr + + + + + + + +
{LN_DETAILS_FORSYSLOGMSG}
+ + \ No newline at end of file diff --git a/src/templates/admin/admin_menu.html b/src/templates/admin/admin_menu.html new file mode 100644 index 0000000..b4f959f --- /dev/null +++ b/src/templates/admin/admin_menu.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +
{LN_ADMINMENU_HOMEPAGE}{LN_ADMINMENU_GENOPT}{LN_ADMINMENU_SOURCEOPT}{LN_ADMINMENU_VIEWSOPT}{LN_ADMINMENU_SEARCHOPT}{LN_ADMINMENU_USEROPT}{LN_ADMINMENU_GROUPOPT} 
diff --git a/src/templates/include_footer.html b/src/templates/include_footer.html index 1723563..7da75ef 100644 --- a/src/templates/include_footer.html +++ b/src/templates/include_footer.html @@ -29,7 +29,7 @@ - +
diff --git a/src/templates/include_header.html b/src/templates/include_header.html index a4314ae..088dfe4 100644 --- a/src/templates/include_header.html +++ b/src/templates/include_header.html @@ -3,8 +3,8 @@ {TITLE} {EXTRA_METATAGS} - - + + {EXTRA_STYLESHEET} {EXTRA_JAVASCRIPT} @@ -13,32 +13,30 @@ - + - - @@ -48,43 +46,48 @@ + @@ -93,21 +96,23 @@ @@ -117,7 +122,14 @@
Satisfied with phpLogCon?
Donate and help keep the project alive!
-
- - - - - -
 {LN_MAIN_SELECTSTYLE}  - - -
-
+
+ + + + + +
 {LN_GEN_LANGUAGE}  + + +
+
-
- - - - - -
 {LN_GEN_LANGUAGE}  - - -
-
+ +
+ + + + + +
 {LN_MAIN_SELECTSTYLE}  + + +
+
+
-
- - - - - -
 {LN_GEN_SELECTSOURCE}  - -
-
+ +
+ + + + + +
 {LN_GEN_SELECTSOURCE}  + +
+
+
-
- - - - - -
 {LN_GEN_SELECTVIEW}  - - -
-
+ +
+ + + + + +
 {LN_GEN_SELECTVIEW}  + + +
+
+
- +
+ + + + + + +
From 736d7edd844c6d32bb83cad444ee7b87246b9bc6 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 15 Jul 2008 10:59:02 +0200 Subject: [PATCH 007/142] Improved menu styles and added admin menu and second sub menu, so you have all options available --- src/include/functions_users.php | 4 +++- src/login.php | 6 +++-- src/templates/admin/admin_menu.html | 27 +++++++++++++--------- src/templates/include_header.html | 13 +++++------ src/templates/include_menu.html | 13 ++++++----- src/templates/index.html | 2 +- src/templates/login.html | 2 +- src/themes/dark/main.css | 35 ++++++++++++++++++++++++----- src/themes/default/main.css | 32 ++++++++++++++++++++++---- 9 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 2e03043..5f7a862 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -160,12 +160,14 @@ function DoLogOff() function RedirectToUserLogin() { + global $content; + // build referer $referer = $_SERVER['PHP_SELF']; if ( isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0 ) $referer .= "?" . $_SERVER['QUERY_STRING']; - header("Location: login.php?referer=" . urlencode($referer) ); + header("Location: " . $content['BASEPATH'] . "login.php?referer=" . urlencode($referer) ); exit; } diff --git a/src/login.php b/src/login.php index 4b0da04..a9e4277 100644 --- a/src/login.php +++ b/src/login.php @@ -54,7 +54,9 @@ $content['pass'] = ""; // Set Referer if ( isset($_GET['referer']) ) - $szRedir = urldecode($_GET['referer']); + $szRedir = $_GET['referer']; +else if ( isset($_POST['referer']) ) + $szRedir = $_POST['referer']; else $szRedir = "index.php"; // Default @@ -79,7 +81,7 @@ if ( isset($_POST['op']) && $_POST['op'] == "login" ) $content['ERROR_MSG'] = $content['LN_LOGIN_ERRWRONGPASSWORD']; } else - RedirectPage( $szRedir ); + RedirectPage( urldecode($szRedir) ); } else { diff --git a/src/templates/admin/admin_menu.html b/src/templates/admin/admin_menu.html index b4f959f..a639dc4 100644 --- a/src/templates/admin/admin_menu.html +++ b/src/templates/admin/admin_menu.html @@ -1,18 +1,25 @@ - - - - - + + + + + + + - - + + - - - + + + + + + + +
{LN_ADMINMENU_HOMEPAGE}{LN_ADMINMENU_GENOPT}{LN_ADMINMENU_SOURCEOPT}{LN_ADMINMENU_VIEWSOPT}{LN_ADMINMENU_SEARCHOPT}{LN_ADMINMENU_GENOPT}{LN_ADMINMENU_SOURCEOPT}{LN_ADMINMENU_VIEWSOPT}{LN_ADMINMENU_SEARCHOPT} {LN_ADMINMENU_USEROPT}{LN_ADMINMENU_GROUPOPT}{LN_ADMINMENU_USEROPT}{LN_ADMINMENU_GROUPOPT}    
diff --git a/src/templates/include_header.html b/src/templates/include_header.html index 088dfe4..c89a45b 100644 --- a/src/templates/include_header.html +++ b/src/templates/include_header.html @@ -22,7 +22,7 @@
-
+ @@ -46,8 +46,7 @@ @@ -97,7 +95,7 @@ - +
 {LN_GEN_LANGUAGE}  - - + @@ -62,7 +61,6 @@
 {LN_MAIN_SELECTSTYLE} 
-
@@ -124,10 +122,11 @@ diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index 4fbb177..bb65c31 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -1,7 +1,8 @@
 {LN_GEN_SELECTVIEW} 
- + - + +
- - - + + + + @@ -10,12 +11,12 @@ - + - - + + diff --git a/src/templates/index.html b/src/templates/index.html index 957e720..c63404f 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -95,7 +95,7 @@
{LN_MENU_SEARCH}{LN_MENU_SHOWEVENTS}
{LN_MENU_SEARCH}{LN_MENU_SHOWEVENTS} {LN_MENU_SEARCHINKB} {LN_MENU_LOGIN}{LN_MENU_LOGIN}   {LN_MENU_ADMINCENTER}{LN_MENU_LOGOFF}{LN_MENU_ADMINCENTER}{LN_MENU_LOGOFF} {LN_MENU_LOGGEDINAS} "{SESSION_USERNAME}"
-
+ {LN_GEN_PAGE} {main_currentpagenumber} diff --git a/src/templates/login.html b/src/templates/login.html index db75825..2f4a71c 100644 --- a/src/templates/login.html +++ b/src/templates/login.html @@ -50,7 +50,7 @@ - +
diff --git a/src/themes/dark/main.css b/src/themes/dark/main.css index 7f7f05b..5f32dda 100644 --- a/src/themes/dark/main.css +++ b/src/themes/dark/main.css @@ -191,6 +191,15 @@ font } /* TOP Menu Classes */ +.topmenu1begin +{ + height: 20px; + border:0px; + padding: 2px 2px 0px 2px; + vertical-align: middle; + + background-color: #38110E; +} .topmenu1 { height: 20px; @@ -206,6 +215,8 @@ font .topmenu1:hover { color: #FFFF99; + border:1px inset; + border-color: #D79993 #290604 #290604 #D79993; background-color: #492D2B; text-decoration: none; } @@ -225,19 +236,32 @@ font color: #FFFFFF; background-color: #290604; } +.topmenu2begin +{ + height: 20px; + border:0px; + padding: 2px 2px 0px 2px; + vertical-align: middle; + + background-color: #49221F; +} .topmenu2 { - height: 16px; + height: 20px; border:1px ridge; border-color: #D79993 #290604 #290604 #D79993; + padding: 2px 2px 0px 2px; + vertical-align: middle; font: 10px Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; - background-color: #38110E; + background-color: #49221F; } .topmenu2:hover { color: #FFFF99; + border:1px inset; + border-color: #D79993 #290604 #290604 #D79993; background-color: #492D2B; text-decoration: none; } @@ -252,12 +276,11 @@ font } .topmenu2end { - height: 16px; - border:1px inset; - border-color: #D79993 #290604 #D79993 #D79993; + height: 20px; + border:0px; font: 10px Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; - background-color: #290604; + background-color: #49221F; } /* Cell Columns */ diff --git a/src/themes/default/main.css b/src/themes/default/main.css index c38750d..1e49e04 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -194,6 +194,15 @@ font } /* TOP Menu Classes */ +.topmenu1begin +{ + height: 20px; + border:0px; + padding: 2px 2px 0px 2px; + vertical-align: middle; + + background-color: #597196; +} .topmenu1 { height: 20px; @@ -209,6 +218,8 @@ font .topmenu1:hover { color: #FFFF99; + border:1px inset; + border-color: #79AABE #09506C #79AABE #79AABE; background-color: #6A88B8; text-decoration: none; } @@ -228,19 +239,32 @@ font color: #FFFFFF; background-color: #597196; } +.topmenu2begin +{ + height: 20px; + border:0px; + padding: 2px 2px 0px 2px; + vertical-align: middle; + + background-color: #7A92A6; +} .topmenu2 { height: 20px; border:1px ridge; - border-color: #79AABE #79AABE #09506C #79AABE; + border-color: #BDEEFF #79AABE #09506C #09506C; + padding: 2px 2px 0px 2px; + vertical-align: middle; font: 10px Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; - background-color: #597196; + background-color: #7A92A6; } .topmenu2:hover { color: #FFFF99; + border:1px inset; + border-color: #BDEEFF #79AABE #09506C #09506C; background-color: #6A88B8; text-decoration: none; } @@ -257,10 +281,10 @@ font { height: 20px; border:1px inset; - border-color: #09506C #79AABE #09506C #09506C; + border-color: #BDEEFF #79AABE #09506C #09506C; font: 10px Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; - background-color: #6A8296; + background-color: #7A92A6; } From 5804574bbfbad27c6e3e297b2d1328e92ca9088e Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 15 Jul 2008 12:28:08 +0200 Subject: [PATCH 008/142] Added admin files for index and user admin. User admin is fully operational now --- src/admin/result.php | 87 +++++++ src/admin/users.php | 361 +++++++++++++++++++++++++++ src/lang/en/admin.php | 68 +++++ src/templates/admin/admin_users.html | 80 ++++++ src/templates/admin/result.html | 20 ++ 5 files changed, 616 insertions(+) create mode 100644 src/admin/result.php create mode 100644 src/admin/users.php create mode 100644 src/lang/en/admin.php create mode 100644 src/templates/admin/admin_users.html create mode 100644 src/templates/admin/result.html diff --git a/src/admin/result.php b/src/admin/result.php new file mode 100644 index 0000000..aa894b6 --- /dev/null +++ b/src/admin/result.php @@ -0,0 +1,87 @@ + Shows ... + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +// include($gl_root_path . 'classes/logstream.class.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); + +// Hardcoded atm +$content['REDIRSECONDS'] = 2; +// *** *** // + +// --- CONTENT Vars +if ( isset($_GET['redir']) ) +{ + $content['EXTRA_METATAGS'] = ''; + $content['SZREDIR'] = urldecode($_GET['redir']); +} +else +{ + $_GET['redir'] = "index.php"; +} + +if ( isset($_GET['msg']) ) + $content['SZMSG'] = urldecode($_GET['msg']); +else + $content['SZMSG'] = $content["LN_ADMIN_UNKNOWNSTATE"]; + +$content['TITLE'] = "phpLogCon - Redirecting to '" . $content['SZREDIR'] . "' in 5 seconds"; // Title of the Page +// --- + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/result.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/admin/users.php b/src/admin/users.php new file mode 100644 index 0000000..3aad54f --- /dev/null +++ b/src/admin/users.php @@ -0,0 +1,361 @@ + Shows ... + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +// include($gl_root_path . 'classes/logstream.class.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); + +// --- CONTENT Vars +$content['TITLE'] = "Ultrastats - Admin Center - Users"; // Title of the Page +// --- + +// --- BEGIN Custom Code +if ($_GET['miniop'] == "setisadmin") +{ + if ( isset($_GET['id']) && isset($_GET['newval']) ) + { + //PreInit these values + $content['USERID'] = intval(DB_RemoveBadChars($_GET['id'])); + + $sqlquery = "SELECT * " . + " FROM " . DB_USERS . + " WHERE ID = " . $content['USERID']; + $result = DB_Query($sqlquery); + $myuser = DB_GetSingleRow($result, true); + if ( isset($myuser['username']) ) + { + $iNewVal = intval(DB_RemoveBadChars($_GET['newval'])); + + // Update is_admin setting! + $result = DB_Query("UPDATE " . DB_USERS . " SET + is_admin = $iNewVal + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = "Error setting is_admin flat, invalid ID, User not found"; + } +} + + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWUSER'] = "true"; + $content['USER_FORMACTION'] = "addnewuser"; + $content['USER_SENDBUTTON'] = $content['LN_USER_ADD']; + + //PreInit these values + $content['USERNAME'] = ""; + $content['PASSWORD1'] = ""; + $content['PASSWORD2'] = ""; + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWUSER'] = "true"; + $content['USER_FORMACTION'] = "edituser"; + $content['USER_SENDBUTTON'] = $content['LN_USER_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['USERID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT * " . + " FROM " . DB_USERS . + " WHERE ID = " . $content['USERID']; + + $result = DB_Query($sqlquery); + $myuser = DB_GetSingleRow($result, true); + if ( isset($myuser['username']) ) + { + $content['USERID'] = $myuser['ID']; + $content['USERNAME'] = $myuser['username']; + + // Set is_admin flag + if ( $myuser['is_admin'] == 1 ) + $content['CHECKED_ISADMIN'] = "checked"; + else + $content['CHECKED_ISADMIN'] = ""; + + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = "*Error, invalid ID, User not found"; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['USERID'] = DB_RemoveBadChars($_GET['id']); + + if ( !isset($_SESSION['SESSION_USERNAME']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_WTFOMFGGG']; + } + else + { + // Get UserInfo + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['username']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + + if ( $_SESSION['SESSION_USERNAME'] == $myrow['username'] ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_DONOTDELURSLF'], $content['USERID'] ); + } + else + { + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_DELUSER'], $content['USERID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENDEL'], $myrow['username'] ) , "users.php" ); + } + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; + } + } + + if ( isset($_POST['op']) ) + { + if ( isset ($_POST['id']) ) { $content['USERID'] = DB_RemoveBadChars($_POST['id']); } else {$content['USERID'] = ""; } + if ( isset ($_POST['username']) ) { $content['USERNAME'] = DB_RemoveBadChars($_POST['username']); } else {$content['USERNAME'] = ""; } + if ( isset ($_POST['password1']) ) { $content['PASSWORD1'] = DB_RemoveBadChars($_POST['password1']); } else {$content['PASSWORD1'] = ""; } + if ( isset ($_POST['password2']) ) { $content['PASSWORD2'] = DB_RemoveBadChars($_POST['password2']); } else {$content['PASSWORD2'] = ""; } + if ( isset ($_POST['isadmin']) ) { $content['ISADMIN'] = 1; } else {$content['ISADMIN'] = 0; } + + + // Check mandotary values + if ( $content['USERNAME'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_USEREMPTY']; + } + + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewuser" ) + { + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $content['USERNAME'] . "'"); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['username']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_USERNAMETAKEN']; + } + else + { + // Check if Password is set! + if ( strlen($content['PASSWORD1']) <= 0 || + $content['PASSWORD1'] != $content['PASSWORD2'] ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; + } + + if ( !isset($content['ISERROR']) ) + { + // Create passwordhash now :)! + $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); + + // Add new User now! + $result = DB_Query("INSERT INTO " . DB_USERS . " (username, password, is_admin) + VALUES ('" . $content['USERNAME'] . "', + '" . $content['PASSWORDHASH'] . "', + " . $content['ISADMIN'] . ")"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENADDED'], $content['USERNAME'] ) , "users.php" ); + } + } + } + else if ( $_POST['op'] == "edituser" ) + { + $result = DB_Query("SELECT ID FROM " . DB_USERS . " WHERE ID = " . $content['USERID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + else + { + + // Check if Password is enabled + if ( isset($content['PASSWORD1']) && strlen($content['PASSWORD1']) > 0 ) + { + if ( $content['PASSWORD1'] != $content['PASSWORD2'] ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; + } + + if ( !isset($content['ISERROR']) ) + { + // Create passwordhash now :)! + $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); + + // Edit the User now! + $result = DB_Query("UPDATE " . DB_USERS . " SET + username = '" . $content['USERNAME'] . "', + password = '" . $content['PASSWORDHASH'] . "', + is_admin = " . $content['ISADMIN'] . " + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + } + else + { + // Edit the User now! + $result = DB_Query("UPDATE " . DB_USERS . " SET + username = '" . $content['USERNAME'] . "', + is_admin = " . $content['ISADMIN'] . " + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENEDIT'], $content['USERNAME']) , "users.php" ); + } + } + } + } +} +else +{ + // Default Mode = List Users + $content['LISTUSERS'] = "true"; + + // Read all Serverentries + $sqlquery = "SELECT ID, " . + " username, " . + " is_admin " . + " FROM " . DB_USERS . + " ORDER BY ID "; + $result = DB_Query($sqlquery); + $content['USERS'] = DB_GetAllRows($result, true); + + // --- Process Users + for($i = 0; $i < count($content['USERS']); $i++) + { + // --- Set Image for IsClanMember + if ( $content['USERS'][$i]['is_admin'] == 1 ) + { + $content['USERS'][$i]['is_isadmin_string'] = $content['MENU_SELECTION_ENABLED']; + $content['USERS'][$i]['set_isadmin'] = 0; + } + else + { + $content['USERS'][$i]['is_isadmin_string'] = $content['MENU_SELECTION_DISABLED']; + $content['USERS'][$i]['set_isadmin'] = 1; + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $content['USERS'][$i]['cssclass'] = "line1"; + else + $content['USERS'][$i]['cssclass'] = "line2"; + // --- + } + // --- +} + +// --- END Custom Code + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_users.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php new file mode 100644 index 0000000..7f38740 --- /dev/null +++ b/src/lang/en/admin.php @@ -0,0 +1,68 @@ + www.phplogcon.org <- + * ----------------------------------------------------------------- + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ +global $content; + +// Global Stuff +$content['LN_ADMINMENU_HOMEPAGE'] = "Back to Show Events"; +$content['LN_ADMINMENU_GENOPT'] = "General Options"; +$content['LN_ADMINMENU_SOURCEOPT'] = "Sources Options"; +$content['LN_ADMINMENU_VIEWSOPT'] = "Views Options"; +$content['LN_ADMINMENU_SEARCHOPT'] = "Search Options"; +$content['LN_ADMINMENU_USEROPT'] = "User Options"; +$content['LN_ADMINMENU_GROUPOPT'] = "Group Options"; +$content['LN_ADMIN_CENTER'] = "Admin center"; +$content['LN_ADMIN_UNKNOWNSTATE'] = "Unknown State"; + +// User Center +$content['LN_USER_CENTER'] = "User Options"; +$content['LN_USER_ID'] = "ID"; +$content['LN_USER_NAME'] = "Username"; +$content['LN_USER_ACTIONS'] = "Available Actions"; +$content['LN_USER_ADD'] = "Add User"; +$content['LN_USER_EDIT'] = "Edit User"; +$content['LN_USER_DELETE'] = "Delete User"; +$content['LN_USER_PASSWORD1'] = "Password"; +$content['LN_USER_PASSWORD2'] = "Confirm Password"; +$content['LN_USER_ERROR_IDNOTFOUND'] = "Error, User with ID '%1' , was not found"; +$content['LN_USER_ERROR_WTFOMFGGG'] = "Error, erm wtf you don't have a username omfg pls mowl?"; +$content['LN_USER_ERROR_DONOTDELURSLF'] = "Error, you can not DELETE YOURSELF!"; +$content['LN_USER_ERROR_DELUSER'] = "Error deleting the User!"; +$content['LN_USER_ERROR_INVALIDID'] = "Error, invalid ID, User not found"; +$content['LN_USER_ERROR_HASBEENDEL'] = "User '%1' has been successfully DELETED!"; +$content['LN_USER_ERROR_USEREMPTY'] = "Error, Username was empty"; +$content['LN_USER_ERROR_USERNAMETAKEN'] = "Error, this Username is already taken!"; +$content['LN_USER_ERROR_PASSSHORT'] = "Error, Password was to short, or did not match"; +$content['LN_USER_ERROR_HASBEENADDED'] = "User '%1' has been successfully added"; +$content['LN_USER_ERROR_HASBEENEDIT'] = "User '%1' has been successfully edited"; +$content['LN_USER_ISADMIN'] = "Is Admin?"; +$content['LN_USER_ADDEDIT'] = "Add/Edit User"; +$content['LN_USER_'] = ""; + + + +?> \ No newline at end of file diff --git a/src/templates/admin/admin_users.html b/src/templates/admin/admin_users.html new file mode 100644 index 0000000..0129d40 --- /dev/null +++ b/src/templates/admin/admin_users.html @@ -0,0 +1,80 @@ + + + +
+

{ERROR_MSG}

+
+ + + + + + + + + +
{LN_USER_CENTER}
+

+ + + + + + + + + + + + + + + + + + + + +
{LN_USER_ID}{LN_USER_NAME}{LN_USER_ISADMIN}{LN_USER_ACTIONS}
{ID}{username} +   +   +
 {LN_USER_ADD}
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_USER_ADDEDIT}
{LN_USER_NAME}
{LN_USER_PASSWORD1}
{LN_USER_PASSWORD2}
{LN_USER_ISADMIN}
+ + + +
+ + + +

+ +
+ + \ No newline at end of file diff --git a/src/templates/admin/result.html b/src/templates/admin/result.html new file mode 100644 index 0000000..8bacd6a --- /dev/null +++ b/src/templates/admin/result.html @@ -0,0 +1,20 @@ + + + + + + + + + +
+ {LN_ADMIN_CENTER}
+ +

+ {SZMSG} +

+ You will be redirected to the this page on {REDIRSECONDS} seconds. + +
+ + \ No newline at end of file From e71e4b7d751eca9005561312eb5bbec144c0fc38 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 15 Jul 2008 15:53:50 +0200 Subject: [PATCH 009/142] Added helper function to prompt user for verification of admin actions This function will be used on other positions in the admin center as well, everytime the user wants to delete something which cannot be undone. --- src/admin/index.php | 5 ++ src/admin/users.php | 36 ++++++++++-- src/include/functions_common.php | 67 +++++++++++++++++++--- src/include/functions_users.php | 11 +++- src/lang/en/admin.php | 8 +++ src/templates/admin/admin_securecheck.html | 32 +++++++++++ src/themes/default/main.css | 6 ++ 7 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 src/templates/admin/admin_securecheck.html diff --git a/src/admin/index.php b/src/admin/index.php index 11b01f6..8eb7cf3 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -115,6 +115,11 @@ else // --- END CREATE TITLE */ +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: General Options"; +// --- END CREATE TITLE + // --- Parsen and Output InitTemplateParser(); $page -> parser($content, "admin/admin_index.html"); diff --git a/src/admin/users.php b/src/admin/users.php index 3aad54f..02f3e0f 100644 --- a/src/admin/users.php +++ b/src/admin/users.php @@ -53,19 +53,31 @@ InitFilterHelpers(); // Helpers for frontend filtering! // Init admin langauge file now! IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); - -// --- CONTENT Vars -$content['TITLE'] = "Ultrastats - Admin Center - Users"; // Title of the Page // --- // --- BEGIN Custom Code + +// Only if the user is an admin! +if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + if ($_GET['miniop'] == "setisadmin") { if ( isset($_GET['id']) && isset($_GET['newval']) ) { //PreInit these values $content['USERID'] = intval(DB_RemoveBadChars($_GET['id'])); + $iNewVal = intval(DB_RemoveBadChars($_GET['newval'])); + // --- handle special case + if ( $content['USERID'] == $content['SESSION_USERID'] && (!isset($_GET['verify']) || $_GET['verify'] != "yes") && $iNewVal == 0) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( $content['LN_USER_WARNREMOVEADMIN'], $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // Perform SQL Query! $sqlquery = "SELECT * " . " FROM " . DB_USERS . " WHERE ID = " . $content['USERID']; @@ -73,8 +85,6 @@ if ($_GET['miniop'] == "setisadmin") $myuser = DB_GetSingleRow($result, true); if ( isset($myuser['username']) ) { - $iNewVal = intval(DB_RemoveBadChars($_GET['newval'])); - // Update is_admin setting! $result = DB_Query("UPDATE " . DB_USERS . " SET is_admin = $iNewVal @@ -181,6 +191,14 @@ if ( isset($_GET['op']) ) } else { + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_USER_WARNDELETEUSER'], $myrow['username'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + // do the delete! $result = DB_Query( "DELETE FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); if ($result == FALSE) @@ -191,6 +209,8 @@ if ( isset($_GET['op']) ) else DB_FreeQuery($result); + // TODO: DELETE PERSONAL SETTINGS, GROUP MEMBERSHIP ... + // Do the final redirect RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENDEL'], $myrow['username'] ) , "users.php" ); } @@ -349,9 +369,13 @@ else } // --- } - // --- END Custom Code +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: User Options"; +// --- END CREATE TITLE + // --- Parsen and Output InitTemplateParser(); $page -> parser($content, "admin/admin_users.html"); diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 9e8996c..071eda9 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -541,12 +541,17 @@ function InitConfigurationValues() // Now we init the user session stuff InitUserSession(); - if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true && !$content['SESSION_LOGGEDIN'] ) - { - // User needs to be logged in, redirect to login page - if ( !defined("IS_LOGINPAGE") ) - RedirectToUserLogin(); - } + if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + { + if ( !$content['SESSION_LOGGEDIN'] ) + { + // User needs to be logged in, redirect to login page + if ( !defined("IS_LOGINPAGE") ) + RedirectToUserLogin(); + } + } + else if ( defined('IS_ADMINPAGE') ) // Language System not initialized yet + DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); // General defaults // // --- Language Handling @@ -559,6 +564,11 @@ function InitConfigurationValues() $content['database_forcedatabaseupdate'] = "yes"; } } + else + { + if ( defined('IS_ADMINPAGE') || defined("IS_LOGINPAGE") ) // Language System not initialized yet + DieWithFriendlyErrorMsg( "The phpLogCon user system is currently disabled or not installed." ); + } // --- Language Handling if ( isset($_SESSION['CUSTOM_LANG']) && VerifyLanguage($_SESSION['CUSTOM_LANG']) ) @@ -711,17 +721,22 @@ function InitPageTitle() else $szReturn = ""; - if ( isset($currentSourceID) && isset($content['Sources'][$currentSourceID]['Name']) ) - $szReturn .= "Source '" . $content['Sources'][$currentSourceID]['Name'] . "' :: "; + if ( !defined('IS_ADMINPAGE') ) + { + if ( isset($currentSourceID) && isset($content['Sources'][$currentSourceID]['Name']) ) + $szReturn .= "Source '" . $content['Sources'][$currentSourceID]['Name'] . "' :: "; + } // Append phpLogCon $szReturn .= "phpLogCon"; + if ( defined('IS_ADMINPAGE') ) + $szReturn .= " :: " . $content['LN_ADMIN_CENTER'] . " :: "; + // return result return $szReturn; } - function GetStringWithHTMLCodes($myStr) { // Replace all special characters with valid html representations @@ -982,4 +997,38 @@ function StartPHPSession() } } +function PrintSecureUserCheck( $warningtext, $yesmsg, $nomsg ) +{ + global $content, $page; + + // Copy properties + $content['warningtext'] = $warningtext; + $content['yesmsg'] = $yesmsg; + $content['nomsg'] = $nomsg; + + // Handle GET and POST input! + $content['form_url'] = $_SERVER['SCRIPT_NAME'] . "?"; + foreach ($_GET as $varname => $varvalue) + $content['form_url'] .= $varname . "=" . $varvalue . "&"; + $content['form_url'] .= "verify=yes"; // Append verify! + + foreach ($_POST as $varname => $varvalue) + $content['POST_VARIABLES'][] = array( "varname" => $varname, "varvalue" => $varvalue ); + + // --- BEGIN CREATE TITLE + $content['TITLE'] = InitPageTitle(); + $content['TITLE'] .= " :: Confirm Action"; + // --- END CREATE TITLE + + // --- Parsen and Output + InitTemplateParser(); + $page -> parser($content, "admin/admin_securecheck.html"); + $page -> output(); + // --- + + // Exit script execution + exit; +} + + ?> \ No newline at end of file diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 5f7a862..01a1510 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -44,6 +44,11 @@ if ( !defined('IN_PHPLOGCON') ) ///include($gl_root_path . 'include/constants_logstream.php'); // --- +// --- Define User System initialized! +define('IS_USERSYSTEMENABLED', true); +$content['IS_USERSYSTEMENABLED'] = true; +// --- + // --- BEGIN Usermanagement Function --- function InitUserSession() { @@ -62,8 +67,9 @@ function InitUserSession() { $content['SESSION_LOGGEDIN'] = true; $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; + $content['SESSION_USERID'] = $_SESSION['SESSION_USERID']; $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; - + // Successfully logged in return true; } @@ -125,10 +131,12 @@ function CheckUserLogin( $username, $password ) { $_SESSION['SESSION_LOGGEDIN'] = true; $_SESSION['SESSION_USERNAME'] = $username; + $_SESSION['SESSION_USERID'] = $myrow['ID']; $_SESSION['SESSION_ISADMIN'] = $myrow['is_admin']; $content['SESSION_LOGGEDIN'] = $_SESSION['SESSION_LOGGEDIN']; $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; + $content['SESSION_USERID'] = $_SESSION['SESSION_USERID']; $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; // TODO SET LAST LOGIN TIME! @@ -152,6 +160,7 @@ function DoLogOff() unset( $_SESSION['SESSION_LOGGEDIN'] ); unset( $_SESSION['SESSION_USERNAME'] ); + unset( $_SESSION['SESSION_USERID'] ); unset( $_SESSION['SESSION_ACCESSLEVEL'] ); // Redir to Index Page diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 7f38740..dfdc53f 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -37,6 +37,9 @@ $content['LN_ADMINMENU_USEROPT'] = "User Options"; $content['LN_ADMINMENU_GROUPOPT'] = "Group Options"; $content['LN_ADMIN_CENTER'] = "Admin center"; $content['LN_ADMIN_UNKNOWNSTATE'] = "Unknown State"; +$content['LN_ADMIN_ERROR_NOTALLOWED'] = "You are not allowed to access this page with your user level."; +$content['LN_DELETEYES'] = "Yes"; +$content['LN_DELETENO'] = "No"; // User Center $content['LN_USER_CENTER'] = "User Options"; @@ -61,6 +64,11 @@ $content['LN_USER_ERROR_HASBEENADDED'] = "User '%1' has been successfully added" $content['LN_USER_ERROR_HASBEENEDIT'] = "User '%1' has been successfully edited"; $content['LN_USER_ISADMIN'] = "Is Admin?"; $content['LN_USER_ADDEDIT'] = "Add/Edit User"; +$content['LN_USER_WARNREMOVEADMIN'] = "You are about to revoke your own administrative priviledges. Are you sure to remove your admin status?"; +$content['LN_USER_WARNDELETEUSER'] = "Are you sure that you want to delete the User '%1'? All his personal settings will be deleted as well."; +$content['LN_USER_'] = ""; +$content['LN_USER_'] = ""; +$content['LN_USER_'] = ""; $content['LN_USER_'] = ""; diff --git a/src/templates/admin/admin_securecheck.html b/src/templates/admin/admin_securecheck.html new file mode 100644 index 0000000..a4f8921 --- /dev/null +++ b/src/templates/admin/admin_securecheck.html @@ -0,0 +1,32 @@ + + +

+ + + + + + + + +
{warningtext}
+
+
+ + + + +
+ +
+ +
+
+ + +
{nomsg} +
+
+
+ + \ No newline at end of file diff --git a/src/themes/default/main.css b/src/themes/default/main.css index 1e49e04..1dd5333 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -445,3 +445,9 @@ select, input, button, textarea font: bold 8pt Arial,Helvetica,sans-serif; color: #BB0000 } + +.borderless +{ + border:0px solid; + background-color: transparent; +} From 251b00ea30ec82385436f45eda9d785913f3a534 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 15 Jul 2008 17:48:39 +0200 Subject: [PATCH 010/142] Added group admin pages and logic. --- src/admin/groups.php | 265 ++++++++++++++++++++++++++ src/admin/users.php | 9 +- src/css/defaults.css | 6 + src/images/icons/businessman_add.png | Bin 0 -> 859 bytes src/include/functions_common.php | 4 + src/lang/en/admin.php | 32 +++- src/templates/admin/admin_groups.html | 78 ++++++++ src/templates/admin/admin_users.html | 8 +- src/themes/default/main.css | 5 - 9 files changed, 385 insertions(+), 22 deletions(-) create mode 100644 src/admin/groups.php create mode 100644 src/images/icons/businessman_add.png create mode 100644 src/templates/admin/admin_groups.html diff --git a/src/admin/groups.php b/src/admin/groups.php new file mode 100644 index 0000000..6fc1f67 --- /dev/null +++ b/src/admin/groups.php @@ -0,0 +1,265 @@ + Helps administrating groups + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +// Only if the user is an admin! +if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWGROUP'] = "true"; + $content['GROUP_FORMACTION'] = "addnewgroup"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_ADD']; + + //PreInit these values + $content['groupname'] = ""; + $content['groupdescription'] = ""; + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWGROUP'] = "true"; + $content['GROUP_FORMACTION'] = "edituser"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT * " . + " FROM " . DB_GROUPS . + " WHERE ID = " . $content['GROUPID']; + + $result = DB_Query($sqlquery); + $myuser = DB_GetSingleRow($result, true); + if ( isset($myuser['groupname']) ) + { + $content['GROUPID'] = $myuser['ID']; + $content['groupname'] = $myuser['groupname']; + $content['groupdescription'] = $myuser['groupdescription']; + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = DB_RemoveBadChars($_GET['id']); + + // Get GroupInfo + $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['groupname']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + else + { + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_GROUP_WARNDELETEGROUP'], $myrow['groupname'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_DELGROUP'], $content['USERID'] ); + } + else + DB_FreeQuery($result); + + // TODO: DELETE GROUP SETTINGS, GROUP MEMBERSHIP ... + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENDEL'], $myrow['groupname'] ) , "groups.php" ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; + } + } + + if ( isset($_POST['op']) ) + { + if ( isset ($_POST['id']) ) { $content['GROUPID'] = DB_RemoveBadChars($_POST['id']); } else {$content['GROUPID'] = ""; } + if ( isset ($_POST['groupname']) ) { $content['groupname'] = DB_RemoveBadChars($_POST['groupname']); } else {$content['groupname'] = ""; } + if ( isset ($_POST['groupdescription']) ) { $content['groupdescription'] = DB_RemoveBadChars($_POST['groupdescription']); } else {$content['groupdescription'] = ""; } + + + // Check mandotary values + if ( $content['groupname'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPEMPTY']; + } + + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewgroup" ) + { + $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE groupname = '" . $content['groupname'] . "'"); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['groupname']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPNAMETAKEN']; + } + else + { + // Add new Group now! + $result = DB_Query("INSERT INTO " . DB_GROUPS . " (groupname, groupdescription) + VALUES ( '" . $content['groupname'] . "', + '" . $content['groupdescription'] . "' )"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_HASBEENADDED'], $content['groupname'] ) , "groups.php" ); + } + } + else if ( $_POST['op'] == "edituser" ) + { + $result = DB_Query("SELECT ID FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + else + { + // Edit the User now! + $result = DB_Query("UPDATE " . DB_GROUPS . " SET + groupname = '" . $content['groupname'] . "', + groupdescription = '" . $content['groupdescription'] . "' + WHERE ID = " . $content['GROUPID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENEDIT'], $content['groupname']) , "groups.php" ); + } + } + } + } +} +else +{ + // Default Mode = List Groups + $content['LISTGROUPS'] = "true"; + + // Read all Serverentries + $sqlquery = "SELECT ID, " . + " groupname, " . + " groupdescription " . + " FROM " . DB_GROUPS. + " ORDER BY ID "; + $result = DB_Query($sqlquery); + $content['GROUPS'] = DB_GetAllRows($result, true); + + if ( count($content['GROUPS']) > 0 ) + { + // --- Process Groups + for($i = 0; $i < count($content['GROUPS']); $i++) + { + // --- Set CSS Class + if ( $i % 2 == 0 ) + $content['GROUPS'][$i]['cssclass'] = "line1"; + else + $content['GROUPS'][$i]['cssclass'] = "line2"; + // --- + } + // --- + } + else + $content['EMPTYGROUPS'] = "true"; +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: Group Options"; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_groups.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/admin/users.php b/src/admin/users.php index 02f3e0f..8dfb82b 100644 --- a/src/admin/users.php +++ b/src/admin/users.php @@ -3,9 +3,9 @@ ********************************************************************* * phpLogCon - http://www.phplogcon.org * ----------------------------------------------------------------- - * Admin Index File + * User Admin File * - * -> Shows ... + * -> Helps administrating users * * All directives are explained within this file * @@ -40,9 +40,6 @@ include($gl_root_path . 'include/functions_common.php'); include($gl_root_path . 'include/functions_frontendhelpers.php'); include($gl_root_path . 'include/functions_filters.php'); -// Include LogStream facility -// include($gl_root_path . 'classes/logstream.class.php'); - // Set PAGE to be ADMINPAGE! define('IS_ADMINPAGE', true); $content['IS_ADMINPAGE'] = true; @@ -158,7 +155,7 @@ if ( isset($_GET['op']) ) else { $content['ISERROR'] = true; - $content['ERROR_MSG'] = "*Error, invalid ID, User not found"; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; } } else if ($_GET['op'] == "delete") diff --git a/src/css/defaults.css b/src/css/defaults.css index 9dc39ec..ebddd41 100644 --- a/src/css/defaults.css +++ b/src/css/defaults.css @@ -69,3 +69,9 @@ { height: 16px; } + +.borderless +{ + border:0px solid; + background-color: transparent; +} diff --git a/src/images/icons/businessman_add.png b/src/images/icons/businessman_add.png new file mode 100644 index 0000000000000000000000000000000000000000..db9ec1e61c81e8acafe394ea9ef708e68f05a619 GIT binary patch literal 859 zcmV-h1ElWdKBPATcx`PH%P~GB7YQATc&NG&4FdHXti7F)%RO1yJn(000McNliru z)&&<0F)A3N=<@&o010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00Of~ zL_t(|+I^F4NK;W5$NzU;r+am|=E~f#bb*Q$Hlbi3O#%zC4`F?Xrr#n7!U}{$=u2;3 zoKiyC7paVt0+R@#w-0_wq>@l9rF_GjI_KP`cdw`CUhhK8I`BK^T+Vs^&w0-M9}b(^ z(>k2y^=MH5y8~+&UGU<5Ll?SfDM-YzO-o5m-Z!CUhXqfJnaJRIC^TxA4OwD1j% zV}iD<6Ktyh|7Vtl+SF7imKFh@4@ihxIdD1^%4qhP_(%l)DG-gpFg6C1+N*KA%7NA8 zIS4CrNFVw{ryQ};maqR6@Oa?0WZO`9_$Zx52Vy*Rc zSSl(iYN3S?5&K)<;;(WjBR6_%?T+f5A1FJAy4l)@!CW$WJ7hmZdK+O^QAquy{8!|5 zJ#^l7YbNGwUtq_&82i%ujztFtSl@Vz^$Z6X++eNiE{3~cZnygZMTjD)kZQXYFgYxt zwo4~+QvALkTz25GM+aV5Le*;?XWr_h%&{1C5ukpx{sNjXLa{a?ok!4HUA)(%Tl9xf zVAbR0pdW0CP_0OTt#vLbI2;b6(N%$XJdQgZZ^Y!4L^rQ9YfGb4;EKRt-KD|zc>%rA zT+FKdQpU^8k0hbS+6vrk@29)L20*rS0tHKCEeE5RUkITjKNHzmN}P3CF>KW0S#bn< zK8}-}s*Mr^3TZ|k_4C9GA#gZQlSj@!hYa&UQ(j?Cx*^M?)M_lEJRw@4? lg)G2iza(8N7UGdd{05!t_|-9PDM0`L002ovPDHLkV1kg}cGCa= literal 0 HcmV?d00001 diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 071eda9..359fcda 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -477,6 +477,10 @@ function InitFrontEndVariables() $content['MENU_KB'] = $content['BASEPATH'] . "images/icons/books.png"; $content['MENU_DOCUMENTVIEW'] = $content['BASEPATH'] . "images/icons/document_view.png"; $content['MENU_DATAEDIT'] = $content['BASEPATH'] . "images/icons/data_edit.png"; + $content['MENU_ADDUSER'] = $content['BASEPATH'] . "images/icons/businessman_add.png"; + $content['MENU_ADD'] = $content['BASEPATH'] . "images/icons/add.png"; + $content['MENU_EDIT'] = $content['BASEPATH'] . "images/icons/edit.png"; + $content['MENU_DELETE'] = $content['BASEPATH'] . "images/icons/delete.png"; $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index dfdc53f..b639a1e 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -40,12 +40,12 @@ $content['LN_ADMIN_UNKNOWNSTATE'] = "Unknown State"; $content['LN_ADMIN_ERROR_NOTALLOWED'] = "You are not allowed to access this page with your user level."; $content['LN_DELETEYES'] = "Yes"; $content['LN_DELETENO'] = "No"; +$content['LN_GEN_ACTIONS'] = "Available Actions"; // User Center $content['LN_USER_CENTER'] = "User Options"; $content['LN_USER_ID'] = "ID"; $content['LN_USER_NAME'] = "Username"; -$content['LN_USER_ACTIONS'] = "Available Actions"; $content['LN_USER_ADD'] = "Add User"; $content['LN_USER_EDIT'] = "Edit User"; $content['LN_USER_DELETE'] = "Delete User"; @@ -54,9 +54,9 @@ $content['LN_USER_PASSWORD2'] = "Confirm Password"; $content['LN_USER_ERROR_IDNOTFOUND'] = "Error, User with ID '%1' , was not found"; $content['LN_USER_ERROR_WTFOMFGGG'] = "Error, erm wtf you don't have a username omfg pls mowl?"; $content['LN_USER_ERROR_DONOTDELURSLF'] = "Error, you can not DELETE YOURSELF!"; -$content['LN_USER_ERROR_DELUSER'] = "Error deleting the User!"; +$content['LN_USER_ERROR_DELUSER'] = "Deleting of the user with id '%1' failed!"; $content['LN_USER_ERROR_INVALIDID'] = "Error, invalid ID, User not found"; -$content['LN_USER_ERROR_HASBEENDEL'] = "User '%1' has been successfully DELETED!"; +$content['LN_USER_ERROR_HASBEENDEL'] = "The User '%1' has been successfully DELETED!"; $content['LN_USER_ERROR_USEREMPTY'] = "Error, Username was empty"; $content['LN_USER_ERROR_USERNAMETAKEN'] = "Error, this Username is already taken!"; $content['LN_USER_ERROR_PASSSHORT'] = "Error, Password was to short, or did not match"; @@ -67,10 +67,28 @@ $content['LN_USER_ADDEDIT'] = "Add/Edit User"; $content['LN_USER_WARNREMOVEADMIN'] = "You are about to revoke your own administrative priviledges. Are you sure to remove your admin status?"; $content['LN_USER_WARNDELETEUSER'] = "Are you sure that you want to delete the User '%1'? All his personal settings will be deleted as well."; $content['LN_USER_'] = ""; -$content['LN_USER_'] = ""; -$content['LN_USER_'] = ""; -$content['LN_USER_'] = ""; - +// Group center +$content['LN_GROUP_ID'] = "ID"; +$content['LN_GROUP_NAME'] = "Groupname"; +$content['LN_GROUP_DESCRIPTION'] = "Groupdescription"; +$content['LN_GROUP_TYPE'] = "Grouptype"; +$content['LN_GROUP_ADD'] = "Add Group"; +$content['LN_GROUP_EDIT'] = "Edit Group"; +$content['LN_GROUP_DELETE'] = "Delete Group"; +$content['LN_GROUP_NOGROUPS'] = "No groups have been added yet"; +$content['LN_GROUP_ADDEDIT'] = "Add/Edit Group"; +$content['LN_GROUP_ERROR_GROUPEMPTY'] = "The groupname cannot be empty."; +$content['LN_GROUP_ERROR_GROUPNAMETAKEN'] = "The groupname has already been taken."; +$content['LN_GROUP_HASBEENADDED'] = "The group '%1' has been successfully added."; +$content['LN_GROUP_ERROR_IDNOTFOUND'] = "The group with ID '%1' could not be found."; +$content['LN_GROUP_ERROR_HASBEENEDIT'] = "The group '%1' has been successfully edited."; +$content['LN_GROUP_ERROR_INVALIDGROUP'] = "Error, invalid ID, Group not found"; +$content['LN_GROUP_WARNDELETEGROUP'] = "Are you sure that you want to delete the Group '%1'? All Groupsettings will be deleted as well."; +$content['LN_GROUP_ERROR_DELGROUP'] = "Deleting of the group with id '%1' failed!"; +$content['LN_GROUP_ERROR_HASBEENDEL'] = "The Group '%1' has been successfully DELETED!"; +$content['LN_GROUP_'] = ""; +$content['LN_GROUP_'] = ""; +$content['LN_GROUP_'] = ""; ?> \ No newline at end of file diff --git a/src/templates/admin/admin_groups.html b/src/templates/admin/admin_groups.html new file mode 100644 index 0000000..8dc9cbb --- /dev/null +++ b/src/templates/admin/admin_groups.html @@ -0,0 +1,78 @@ + + + +
+

{ERROR_MSG}

+
+ + + + + + + + + +
{LN_USER_CENTER}
+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_GROUP_ID}{LN_GROUP_NAME}{LN_GROUP_DESCRIPTION}{LN_GEN_ACTIONS}
{LN_GROUP_NOGROUPS}
{ID}{groupname}{groupdescription} +   +   +   +
 {LN_GROUP_ADD}
+ + + +
+ + + + + + + + + + + + + + +
{LN_GROUP_ADDEDIT}
{LN_GROUP_NAME}
{LN_GROUP_DESCRIPTION}
+ + + +
+
+ + +

+ +
+ + \ No newline at end of file diff --git a/src/templates/admin/admin_users.html b/src/templates/admin/admin_users.html index 0129d40..6f994a4 100644 --- a/src/templates/admin/admin_users.html +++ b/src/templates/admin/admin_users.html @@ -20,7 +20,7 @@
{LN_USER_ID} {LN_USER_NAME} {LN_USER_ISADMIN}{LN_USER_ACTIONS}{LN_GEN_ACTIONS}
{username} -   -   +   +  
 {LN_USER_ADD} {LN_USER_ADD}
diff --git a/src/themes/default/main.css b/src/themes/default/main.css index 1dd5333..44eec8d 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -446,8 +446,3 @@ select, input, button, textarea color: #BB0000 } -.borderless -{ - border:0px solid; - background-color: transparent; -} From d9396f9b5093729d2b3299a8d40662276ffabb38 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 16 Jul 2008 16:23:10 +0200 Subject: [PATCH 011/142] Added support to add and remove users to groups, plus lots of error handling --- src/admin/groups.php | 327 ++++++++++++++++++++---- src/admin/users.php | 161 ++++++------ src/images/icons/businessman_delete.png | Bin 0 -> 872 bytes src/include/functions_common.php | 9 +- src/lang/en/admin.php | 15 +- src/templates/admin/admin_groups.html | 92 ++++++- src/templates/admin/admin_users.html | 2 +- 7 files changed, 460 insertions(+), 146 deletions(-) create mode 100644 src/images/icons/businessman_delete.png diff --git a/src/admin/groups.php b/src/admin/groups.php index 6fc1f67..2858456 100644 --- a/src/admin/groups.php +++ b/src/admin/groups.php @@ -71,11 +71,143 @@ if ( isset($_GET['op']) ) $content['groupname'] = ""; $content['groupdescription'] = ""; } + else if ($_GET['op'] == "adduser" && isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = intval( DB_RemoveBadChars($_GET['id']) ); + + // Set Mode to add + $content['ISADDUSER'] = "true"; + $content['GROUP_FORMACTION'] = "adduser"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_ADDUSER']; + + // --- Get Groupname + $sqlquery = "SELECT " . + DB_GROUPS . ".groupname " . + " FROM " . DB_GROUPS . + " WHERE " . DB_GROUPS . ".id = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $tmparray = DB_GetSingleRow($result, true); + + if ( isset($tmparray) ) + { + // Copy Groupname + $content['GROUPNAME'] = $tmparray['groupname']; + + // --- Get Group Members + $sqlquery = "SELECT " . + DB_GROUPMEMBERS. ".userid " . + " FROM " . DB_GROUPMEMBERS . + " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $tmparray = DB_GetAllRows($result, true); + if ( count($tmparray) > 0 ) + { + // Add UserID's to where clause! + foreach ($tmparray as $datarow) + { + if ( isset($whereclause) ) + $whereclause .= ", " . $datarow['userid']; + else + $whereclause = " WHERE " . DB_USERS . ".id NOT IN (" . $datarow['userid']; + } + // Finish whereclause + $whereclause .= ") "; + } + else + $whereclause = ""; + // --- + + // --- Create LIST of Users which are available for selection + $sqlquery = "SELECT " . + DB_USERS. ".ID as userid, " . + DB_USERS. ".username " . + " FROM " . DB_USERS . + " LEFT OUTER JOIN (" . DB_GROUPMEMBERS . + ") ON (" . + DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . + $whereclause . + " ORDER BY " . DB_USERS . ".username"; + $result = DB_Query($sqlquery); + $content['SUBUSERS'] = DB_GetAllRows($result, true); + + if ( count($content['SUBUSERS']) <= 0 ) + { + // Disable FORM: + $content['ISADDUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERRORNOMOREUSERS'], $content['GROUPNAME'] ); + } + } + else + { + // Disable FORM: + $content['ISADDUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + // --- + } + else if ($_GET['op'] == "removeuser" && isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = intval( DB_RemoveBadChars($_GET['id']) ); + + // Set Mode to add + $content['ISREMOVEUSER'] = "true"; + $content['GROUP_FORMACTION'] = "removeuser"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_USERDELETE']; + + // --- Get Groupname + $sqlquery = "SELECT " . + DB_GROUPS . ".groupname " . + " FROM " . DB_GROUPS . + " WHERE " . DB_GROUPS . ".id = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $tmparray = DB_GetSingleRow($result, true); + + if ( isset($tmparray) ) + { + // Copy Groupname + $content['GROUPNAME'] = $tmparray['groupname']; + + // --- Get Group Members + $sqlquery = "SELECT " . + DB_GROUPMEMBERS. ".userid, " . + DB_USERS. ".username " . + " FROM " . DB_GROUPMEMBERS . + " INNER JOIN (" . DB_USERS . + ") ON (" . + DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . + " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $content['SUBRMUSERS'] = DB_GetAllRows($result, true); + if ( count($content['SUBRMUSERS']) <= 0 ) + { + // Disable FORM: + $content['ISREMOVEUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERRORNOUSERSINGROUP'], $content['GROUPNAME'] ); + } + } + else + { + // Disable FORM: + $content['ISREMOVEUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + + } else if ($_GET['op'] == "edit") { // Set Mode to edit $content['ISEDITORNEWGROUP'] = "true"; - $content['GROUP_FORMACTION'] = "edituser"; + $content['GROUP_FORMACTION'] = "editgroup"; $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_EDIT']; if ( isset($_GET['id']) ) @@ -154,76 +286,145 @@ if ( isset($_GET['op']) ) $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; } } +} - if ( isset($_POST['op']) ) +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['GROUPID'] = intval( DB_RemoveBadChars($_POST['id']) ); } else {$content['GROUPID'] = ""; } + if ( isset ($_POST['groupname']) ) { $content['groupname'] = DB_RemoveBadChars($_POST['groupname']); } else {$content['groupname'] = ""; } + if ( isset ($_POST['groupdescription']) ) { $content['groupdescription'] = DB_RemoveBadChars($_POST['groupdescription']); } else {$content['groupdescription'] = ""; } + + // Check mandotary values + if ( $content['groupname'] == "" ) { - if ( isset ($_POST['id']) ) { $content['GROUPID'] = DB_RemoveBadChars($_POST['id']); } else {$content['GROUPID'] = ""; } - if ( isset ($_POST['groupname']) ) { $content['groupname'] = DB_RemoveBadChars($_POST['groupname']); } else {$content['groupname'] = ""; } - if ( isset ($_POST['groupdescription']) ) { $content['groupdescription'] = DB_RemoveBadChars($_POST['groupdescription']); } else {$content['groupdescription'] = ""; } + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPEMPTY']; + } - - // Check mandotary values - if ( $content['groupname'] == "" ) + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewgroup" ) { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPEMPTY']; - } - - if ( !isset($content['ISERROR']) ) - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewgroup" ) + $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE groupname = '" . $content['groupname'] . "'"); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['groupname']) ) { - $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE groupname = '" . $content['groupname'] . "'"); + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPNAMETAKEN']; + } + else + { + // Add new Group now! + $result = DB_Query("INSERT INTO " . DB_GROUPS . " (groupname, groupdescription) + VALUES ( '" . $content['groupname'] . "', + '" . $content['groupdescription'] . "' )"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_HASBEENADDED'], $content['groupname'] ) , "groups.php" ); + } + } + else if ( $_POST['op'] == "editgroup" ) + { + $result = DB_Query("SELECT ID FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + else + { + // Edit the User now! + $result = DB_Query("UPDATE " . DB_GROUPS . " SET + groupname = '" . $content['groupname'] . "', + groupdescription = '" . $content['groupdescription'] . "' + WHERE ID = " . $content['GROUPID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENEDIT'], $content['groupname']) , "groups.php" ); + } + } + else if ( $_POST['op'] == "adduser" ) + { + if ( isset($_POST['userid']) ) + { + // Copy UserID + $content['USERID'] = intval( DB_RemoveBadChars($_POST['userid']) ); + + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE id = " . $content['USERID']); $myrow = DB_GetSingleRow($result, true); - if ( isset($myrow['groupname']) ) + if ( isset($myrow['username']) ) { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPNAMETAKEN']; - } - else - { - // Add new Group now! - $result = DB_Query("INSERT INTO " . DB_GROUPS . " (groupname, groupdescription) - VALUES ( '" . $content['groupname'] . "', - '" . $content['groupdescription'] . "' )"); + // Add Groupmembership now! + $result = DB_Query("INSERT INTO " . DB_GROUPMEMBERS . " (groupid, userid, is_member) + VALUES ( " . $content['GROUPID'] . ", + " . $content['USERID'] . ", + 1 )"); DB_FreeQuery($result); // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_HASBEENADDED'], $content['groupname'] ) , "groups.php" ); - } - } - else if ( $_POST['op'] == "edituser" ) - { - $result = DB_Query("SELECT ID FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_USERHASBEENADDEDGROUP'], $myrow['username'], $content['groupname'] ) , "groups.php" ); } else { - // Edit the User now! - $result = DB_Query("UPDATE " . DB_GROUPS . " SET - groupname = '" . $content['groupname'] . "', - groupdescription = '" . $content['groupdescription'] . "' - WHERE ID = " . $content['GROUPID']); - DB_FreeQuery($result); - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENEDIT'], $content['groupname']) , "groups.php" ); + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_USERIDMISSING']; + } + } + else if ( $_POST['op'] == "removeuser" ) + { + if ( isset($_POST['userid']) ) + { + // Copy UserID + $content['USERID'] = intval( DB_RemoveBadChars($_POST['userid']) ); + + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE id = " . $content['USERID']); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['username']) ) + { + // remove user from group + $result = DB_Query( "DELETE FROM " . DB_GROUPMEMBERS . " WHERE userid = " . $content['USERID'] . " AND groupid = " . $content['GROUPID']); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_REMUSERFROMGROUP'], $myrow['username'], $content['groupname'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_USERHASBEENREMOVED'], $myrow['username'], $content['groupname'] ) , "groups.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_USERIDMISSING']; } } } } -else + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) { // Default Mode = List Groups $content['LISTGROUPS'] = "true"; - // Read all Serverentries + // Read all Groupentries $sqlquery = "SELECT ID, " . " groupname, " . " groupdescription " . @@ -243,6 +444,34 @@ else else $content['GROUPS'][$i]['cssclass'] = "line2"; // --- + + // --- Read all Memberentries for this group + $sqlquery = "SELECT " . + DB_USERS. ".username, " . + DB_GROUPMEMBERS . ".userid, " . + DB_GROUPMEMBERS . ".groupid, " . + DB_GROUPMEMBERS . ".is_member " . + " FROM " . DB_GROUPMEMBERS . + " INNER JOIN (" . DB_USERS . + ") ON (" . + DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . + " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPS'][$i]['ID'] . + " ORDER BY " . DB_USERS . ".username"; + $result = DB_Query($sqlquery); + $content['GROUPS'][$i]['USERS'] = DB_GetAllRows($result, true); + + if ( count($content['GROUPS'][$i]['USERS']) > 0 ) + { + // Enable Groupmembers + $content['GROUPS'][$i]['GROUPMEMBERS'] = true; + + // Process Groups + $subUserCount = count($content['GROUPS'][$i]['USERS']); + for($j = 0; $j < $subUserCount; $j++) + $content['GROUPS'][$i]['USERS'][$j]['seperator'] = ", "; + $content['GROUPS'][$i]['USERS'][$subUserCount-1]['seperator'] = ""; // last one is empty + } + // --- } // --- } diff --git a/src/admin/users.php b/src/admin/users.php index 8dfb82b..817074b 100644 --- a/src/admin/users.php +++ b/src/admin/users.php @@ -168,7 +168,7 @@ if ( isset($_GET['op']) ) if ( !isset($_SESSION['SESSION_USERNAME']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_WTFOMFGGG']; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDSESSIONS']; } else { @@ -219,115 +219,116 @@ if ( isset($_GET['op']) ) $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; } } +} - if ( isset($_POST['op']) ) +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['USERID'] = DB_RemoveBadChars($_POST['id']); } else {$content['USERID'] = ""; } + if ( isset ($_POST['username']) ) { $content['USERNAME'] = DB_RemoveBadChars($_POST['username']); } else {$content['USERNAME'] = ""; } + if ( isset ($_POST['password1']) ) { $content['PASSWORD1'] = DB_RemoveBadChars($_POST['password1']); } else {$content['PASSWORD1'] = ""; } + if ( isset ($_POST['password2']) ) { $content['PASSWORD2'] = DB_RemoveBadChars($_POST['password2']); } else {$content['PASSWORD2'] = ""; } + if ( isset ($_POST['isadmin']) ) { $content['ISADMIN'] = 1; } else {$content['ISADMIN'] = 0; } + + + // Check mandotary values + if ( $content['USERNAME'] == "" ) { - if ( isset ($_POST['id']) ) { $content['USERID'] = DB_RemoveBadChars($_POST['id']); } else {$content['USERID'] = ""; } - if ( isset ($_POST['username']) ) { $content['USERNAME'] = DB_RemoveBadChars($_POST['username']); } else {$content['USERNAME'] = ""; } - if ( isset ($_POST['password1']) ) { $content['PASSWORD1'] = DB_RemoveBadChars($_POST['password1']); } else {$content['PASSWORD1'] = ""; } - if ( isset ($_POST['password2']) ) { $content['PASSWORD2'] = DB_RemoveBadChars($_POST['password2']); } else {$content['PASSWORD2'] = ""; } - if ( isset ($_POST['isadmin']) ) { $content['ISADMIN'] = 1; } else {$content['ISADMIN'] = 0; } + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_USEREMPTY']; + } - - // Check mandotary values - if ( $content['USERNAME'] == "" ) + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewuser" ) { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_USEREMPTY']; - } - - if ( !isset($content['ISERROR']) ) - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewuser" ) + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $content['USERNAME'] . "'"); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['username']) ) { - $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $content['USERNAME'] . "'"); - $myrow = DB_GetSingleRow($result, true); - if ( isset($myrow['username']) ) + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_USERNAMETAKEN']; + } + else + { + // Check if Password is set! + if ( strlen($content['PASSWORD1']) <= 0 || + $content['PASSWORD1'] != $content['PASSWORD2'] ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_USERNAMETAKEN']; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; } - else + + if ( !isset($content['ISERROR']) ) + { + // Create passwordhash now :)! + $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); + + // Add new User now! + $result = DB_Query("INSERT INTO " . DB_USERS . " (username, password, is_admin) + VALUES ('" . $content['USERNAME'] . "', + '" . $content['PASSWORDHASH'] . "', + " . $content['ISADMIN'] . ")"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENADDED'], $content['USERNAME'] ) , "users.php" ); + } + } + } + else if ( $_POST['op'] == "edituser" ) + { + $result = DB_Query("SELECT ID FROM " . DB_USERS . " WHERE ID = " . $content['USERID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + else + { + + // Check if Password is enabled + if ( isset($content['PASSWORD1']) && strlen($content['PASSWORD1']) > 0 ) { - // Check if Password is set! - if ( strlen($content['PASSWORD1']) <= 0 || - $content['PASSWORD1'] != $content['PASSWORD2'] ) + if ( $content['PASSWORD1'] != $content['PASSWORD2'] ) { $content['ISERROR'] = true; $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; } if ( !isset($content['ISERROR']) ) - { + { // Create passwordhash now :)! $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); - // Add new User now! - $result = DB_Query("INSERT INTO " . DB_USERS . " (username, password, is_admin) - VALUES ('" . $content['USERNAME'] . "', - '" . $content['PASSWORDHASH'] . "', - " . $content['ISADMIN'] . ")"); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENADDED'], $content['USERNAME'] ) , "users.php" ); - } - } - } - else if ( $_POST['op'] == "edituser" ) - { - $result = DB_Query("SELECT ID FROM " . DB_USERS . " WHERE ID = " . $content['USERID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - else - { - - // Check if Password is enabled - if ( isset($content['PASSWORD1']) && strlen($content['PASSWORD1']) > 0 ) - { - if ( $content['PASSWORD1'] != $content['PASSWORD2'] ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; - } - - if ( !isset($content['ISERROR']) ) - { - // Create passwordhash now :)! - $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); - - // Edit the User now! - $result = DB_Query("UPDATE " . DB_USERS . " SET - username = '" . $content['USERNAME'] . "', - password = '" . $content['PASSWORDHASH'] . "', - is_admin = " . $content['ISADMIN'] . " - WHERE ID = " . $content['USERID']); - DB_FreeQuery($result); - } - } - else - { // Edit the User now! $result = DB_Query("UPDATE " . DB_USERS . " SET username = '" . $content['USERNAME'] . "', + password = '" . $content['PASSWORDHASH'] . "', is_admin = " . $content['ISADMIN'] . " WHERE ID = " . $content['USERID']); DB_FreeQuery($result); } - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENEDIT'], $content['USERNAME']) , "users.php" ); } + else + { + // Edit the User now! + $result = DB_Query("UPDATE " . DB_USERS . " SET + username = '" . $content['USERNAME'] . "', + is_admin = " . $content['ISADMIN'] . " + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENEDIT'], $content['USERNAME']) , "users.php" ); } } } } -else + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) { // Default Mode = List Users $content['LISTUSERS'] = "true"; diff --git a/src/images/icons/businessman_delete.png b/src/images/icons/businessman_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..52e201944f71ba288b4a371b8d56c7dc91898a7c GIT binary patch literal 872 zcmV-u1DE`XP)WdKBPATcx`PH%P~GB7YQATc&NG&4FdHXti7F)%RO1yJn(000McNliru z)&&<0F)A3N=<@&o010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00O{C zL_t(|+I^E*NK|1I$N%@v;y7Egn3GLfsb$(CkwTK0xD-@Gq2Ytl0zMVKC}j}!5YPh>b zFiI&wf?v*;uG&5UXJj(4Kjr|p8;A+13>Zx!4p7ufbQ0cw3WOuj4GjVLc_&eRtPmcT z1EF~b6x}`4?LZo(^#50YycFg?|@Qk zN=hGvNuAF+o_18-$egm9X#^eM=*Ri572%#umSiO5hD95hKdmz;J$zjO|LVeJt~GBj z&)03|pJ(Ur-0(Q>YdFR4eh}i*u8;G^OJBYrr}_|;XqH8Obuj7*Oh6|`de_6`%AE^% zW1WV%#R|)N7gV#|2qOlaf 0 ) - $strfinal = str_replace ( "%1", $param2, $strfinal ); + $strfinal = str_replace ( "%2", $param2, $strfinal ); if ( strlen($param3) > 0 ) - $strfinal = str_replace ( "%1", $param3, $strfinal ); + $strfinal = str_replace ( "%3", $param3, $strfinal ); if ( strlen($param4) > 0 ) - $strfinal = str_replace ( "%1", $param4, $strfinal ); + $strfinal = str_replace ( "%4", $param4, $strfinal ); if ( strlen($param5) > 0 ) - $strfinal = str_replace ( "%1", $param5, $strfinal ); + $strfinal = str_replace ( "%5", $param5, $strfinal ); // And return return $strfinal; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index b639a1e..c4d8de7 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -52,7 +52,6 @@ $content['LN_USER_DELETE'] = "Delete User"; $content['LN_USER_PASSWORD1'] = "Password"; $content['LN_USER_PASSWORD2'] = "Confirm Password"; $content['LN_USER_ERROR_IDNOTFOUND'] = "Error, User with ID '%1' , was not found"; -$content['LN_USER_ERROR_WTFOMFGGG'] = "Error, erm wtf you don't have a username omfg pls mowl?"; $content['LN_USER_ERROR_DONOTDELURSLF'] = "Error, you can not DELETE YOURSELF!"; $content['LN_USER_ERROR_DELUSER'] = "Deleting of the user with id '%1' failed!"; $content['LN_USER_ERROR_INVALIDID'] = "Error, invalid ID, User not found"; @@ -66,9 +65,11 @@ $content['LN_USER_ISADMIN'] = "Is Admin?"; $content['LN_USER_ADDEDIT'] = "Add/Edit User"; $content['LN_USER_WARNREMOVEADMIN'] = "You are about to revoke your own administrative priviledges. Are you sure to remove your admin status?"; $content['LN_USER_WARNDELETEUSER'] = "Are you sure that you want to delete the User '%1'? All his personal settings will be deleted as well."; +$content['LN_USER_ERROR_INVALIDSESSIONS'] = "Invalid User Session."; $content['LN_USER_'] = ""; // Group center +$content['LN_GROUP_CENTER'] = "Group Center"; $content['LN_GROUP_ID'] = "ID"; $content['LN_GROUP_NAME'] = "Groupname"; $content['LN_GROUP_DESCRIPTION'] = "Groupdescription"; @@ -87,8 +88,16 @@ $content['LN_GROUP_ERROR_INVALIDGROUP'] = "Error, invalid ID, Group not found"; $content['LN_GROUP_WARNDELETEGROUP'] = "Are you sure that you want to delete the Group '%1'? All Groupsettings will be deleted as well."; $content['LN_GROUP_ERROR_DELGROUP'] = "Deleting of the group with id '%1' failed!"; $content['LN_GROUP_ERROR_HASBEENDEL'] = "The Group '%1' has been successfully DELETED!"; -$content['LN_GROUP_'] = ""; -$content['LN_GROUP_'] = ""; +$content['LN_GROUP_MEMBERS'] = "Groupmembers: "; +$content['LN_GROUP_ADDUSER'] = "Add User to Group"; +$content['LN_GROUP_ERROR_USERIDMISSING'] = "The userid is missing."; +$content['LN_GROUP_USERHASBEENADDEDGROUP'] = "The User '%1' has been successfully added to group '%2'"; +$content['LN_GROUP_ERRORNOMOREUSERS'] = "There are no more available users who can be added to the group '%1'"; +$content['LN_GROUP_USER_ADD'] = "Add User to the group"; +$content['LN_GROUP_USERDELETE'] = "Remove a User from the group"; +$content['LN_GROUP_ERRORNOUSERSINGROUP'] = "There are no users to remove in this the group '%1'"; +$content['LN_GROUP_ERROR_REMUSERFROMGROUP'] = "The user '%1' could not be removed from the group '%2'"; +$content['LN_GROUP_USERHASBEENREMOVED'] = "The user '%1' has been successfully removed from the group '%2'"; $content['LN_GROUP_'] = ""; ?> \ No newline at end of file diff --git a/src/templates/admin/admin_groups.html b/src/templates/admin/admin_groups.html index 8dc9cbb..653f9bd 100644 --- a/src/templates/admin/admin_groups.html +++ b/src/templates/admin/admin_groups.html @@ -1,17 +1,19 @@ - -

-

{ERROR_MSG}

-
- - - + + + + + + + + @@ -46,7 +62,7 @@ - +
{LN_USER_CENTER}{LN_GROUP_CENTER}
+ + +

+
+

{ERROR_MSG}

+
+ +

@@ -33,11 +35,25 @@
{groupname} {groupdescription} -   +   +   +    
{LN_GROUP_MEMBERS} + + + {username}{seperator} + +
 {LN_GROUP_ADD}
@@ -69,6 +85,64 @@ + + + + + +
{LN_GROUP_ADDEDIT}
+ + + + + + + + + +
{LN_GROUP_ADDUSER}: '{GROUPNAME}'
{LN_USER_NAME} + +
+ + + +
+ + + + +
+ + + + + + + + + + + + + +
{LN_GROUP_USERDELETE}: '{GROUPNAME}'
{LN_USER_NAME} + +
+ + + +
+
+ +

diff --git a/src/templates/admin/admin_users.html b/src/templates/admin/admin_users.html index 6f994a4..f4f9173 100644 --- a/src/templates/admin/admin_users.html +++ b/src/templates/admin/admin_users.html @@ -40,7 +40,7 @@ -
+ From 5b2d503e7e20e2ff1c99ae51a5e9f012e2df3047 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 16 Jul 2008 17:25:00 +0200 Subject: [PATCH 012/142] Started adding main options into the template --- src/include/functions_themes.php | 1 + src/lang/en/admin.php | 23 ++++++ src/templates/admin/admin_index.html | 106 ++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 3 deletions(-) diff --git a/src/include/functions_themes.php b/src/include/functions_themes.php index 46990fd..c87a107 100644 --- a/src/include/functions_themes.php +++ b/src/include/functions_themes.php @@ -64,6 +64,7 @@ function CreateLanguageList() // Init Language DisplayName $content['USERLANG'][$i]['DisplayName'] = GetLanguageDisplayName( $alldirectories[$i] ); + $content['LANGUAGES'][$i]['DisplayName'] = GetLanguageDisplayName( $alldirectories[$i] ); } } diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index c4d8de7..300886e 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -41,6 +41,29 @@ $content['LN_ADMIN_ERROR_NOTALLOWED'] = "You are not allowed to access this page $content['LN_DELETEYES'] = "Yes"; $content['LN_DELETENO'] = "No"; $content['LN_GEN_ACTIONS'] = "Available Actions"; +$content['LN_ADMIN_SEND'] = "Send changes"; + +// General Options +$content['LN_ADMIN_MISC'] = "Miscellaneous Options"; +$content['LN_GEN_SHOWDEBUGMSG'] = "Show Debug messages"; +$content['LN_GEN_DEBUGGRIDCOUNTER'] = "Show Debug Gridcounter"; +$content['LN_GEN_SHOWPAGERENDERSTATS'] = "Show Pagerenderstats"; +$content['LN_GEN_ENABLEGZIP'] = "Enable GZIP Compressed Output"; +$content['LN_GEN_DEBUGUSERLOGIN'] = "Debug Userlogin"; +$content['LN_ADMIN_FRONTEND'] = "Frontend Options"; +$content['LN_GEN_WEBSTYLE'] = "Default selected style"; +$content['LN_GEN_SELLANGUAGE'] = "Default selected language"; +$content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; +$content['LN_GEN_USETODAY'] = "Use Today and Yesterday in timefields"; +$content['LN_GEN_DETAILPOPUPS'] = "Use Popup to display the full messagedetails"; +$content['LN_GEN_MSGCHARLIMIT'] = "Character limit of the message in main view"; +$content['LN_GEN_ENTRIESPERPAGE'] = "Number of entries per page"; +$content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; +$content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; +$content['LN_GEN_CUSTBTNCAPT'] = "Custom search caption"; +$content['LN_GEN_CUSTBTNSRCH'] = "Custom search string"; +$content['LN_GEN_'] = ""; +$content['LN_GEN_'] = ""; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 9446a1c..78c9bac 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -2,10 +2,110 @@
{LN_USER_ADDEDIT}
- + - -
{LN_DETAILS_FORSYSLOGMSG}{LN_ADMINMENU_GENOPT}
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {LN_ADMIN_FRONTEND}
{LN_GEN_WEBSTYLE} + +
{LN_GEN_SELLANGUAGE} + +
{LN_GEN_PREPENDTITLE}
{LN_GEN_MSGCHARLIMIT}
{LN_GEN_ENTRIESPERPAGE}
{LN_GEN_AUTORELOADSECONDS}
{LN_GEN_CUSTBTNCAPT}
{LN_GEN_CUSTBTNSRCH}
{LN_GEN_USETODAY}
{LN_GEN_DETAILPOPUPS}
{LN_GEN_IPADRRESOLVE}
+ {LN_ADMIN_MISC}
{LN_GEN_SHOWDEBUGMSG}
{LN_GEN_DEBUGGRIDCOUNTER}
{LN_GEN_SHOWPAGERENDERSTATS}
{LN_GEN_ENABLEGZIP}
{LN_GEN_DEBUGUSERLOGIN}
+ + +
+ +
+ +

+ \ No newline at end of file From d5698cd5de48577974c266f77525a92009c01996 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 17 Jul 2008 11:13:13 +0200 Subject: [PATCH 013/142] General Options can now be saved, and are also loaded from DB now. Settings within the DB will overwrite settings made in config.php --- src/admin/index.php | 118 +++++++++++++++++++++-------------- src/include/functions_db.php | 13 ++-- src/lang/en/admin.php | 2 +- 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index 8eb7cf3..ba285ba 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -60,60 +60,84 @@ IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); //$content['EXTRA_STYLESHEET'] .= ''; // --- -// --- CONTENT Vars -/* -if ( isset($_GET['uid']) ) - $content['uid_current'] = intval($_GET['uid']); -else - $content['uid_current'] = UID_UNKNOWN; +// --- BEGIN Custom Code -// Copy UID for later use ... -$content['uid_fromgetrequest'] = $content['uid_current']; - -// Init Pager variables -$content['uid_first'] = UID_UNKNOWN; -$content['uid_last'] = UID_UNKNOWN; -$content['main_pagerenabled'] = false; -$content['main_pager_first_found'] = false; -$content['main_pager_previous_found'] = false; -$content['main_pager_next_found'] = false; -$content['main_pager_last_found'] = false; - -// Set Default reading direction -$content['read_direction'] = EnumReadDirection::Backward; - -// If set read direction property! -if ( isset($_GET['direction']) ) +// Check for changes first +if ( isset($_POST['op']) ) { - if ( $_GET['direction'] == "next" ) + if ( $_POST['op'] == "edit" ) { - $content['skiprecords'] = 1; - $content['read_direction'] = EnumReadDirection::Backward; - } - else if ( $_GET['direction'] == "previous" ) - { - $content['skiprecords'] = 1; - $content['read_direction'] = EnumReadDirection::Forward; + // Language needs special treatment + if ( isset ($_POST['ViewDefaultLanguage']) ) + { + $tmpvar = DB_RemoveBadChars($_POST['ViewDefaultLanguage']); + if ( VerifyLanguage($tmpvar) ) + $content['ViewDefaultLanguage'] = $tmpvar; + } + + // Read default theme + if ( isset ($_POST['ViewDefaultTheme']) ) { $content['ViewDefaultTheme'] = DB_RemoveBadChars($_POST['ViewDefaultTheme']); } + + // Read checkboxes + if ( isset ($_POST['ViewUseTodayYesterday']) ) { $content['ViewUseTodayYesterday'] = 1; } else { $content['ViewUseTodayYesterday'] = 0; } + if ( isset ($_POST['ViewEnableDetailPopups']) ) { $content['ViewEnableDetailPopups'] = 1; } else { $content['ViewEnableDetailPopups'] = 0; } + if ( isset ($_POST['EnableIPAddressResolve']) ) { $content['EnableIPAddressResolve'] = 1; } else { $content['EnableIPAddressResolve'] = 0; } + if ( isset ($_POST['MiscShowDebugMsg']) ) { $content['MiscShowDebugMsg'] = 1; } else { $content['MiscShowDebugMsg'] = 0; } + if ( isset ($_POST['MiscShowDebugGridCounter']) ) { $content['MiscShowDebugGridCounter'] = 1; } else { $content['MiscShowDebugGridCounter'] = 0; } + if ( isset ($_POST['MiscShowPageRenderStats']) ) { $content['MiscShowPageRenderStats'] = 1; } else { $content['MiscShowPageRenderStats'] = 0; } + if ( isset ($_POST['MiscEnableGzipCompression']) ) { $content['MiscEnableGzipCompression'] = 1; } else { $content['MiscEnableGzipCompression'] = 0; } + if ( isset ($_POST['DebugUserLogin']) ) { $content['DebugUserLogin'] = 1; } else { $content['DebugUserLogin'] = 0; } + + // Read Text number fields + if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = DB_RemoveBadChars($_POST['ViewMessageCharacterLimit']); } + if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = DB_RemoveBadChars($_POST['ViewEntriesPerPage']); } + if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = DB_RemoveBadChars($_POST['ViewEnableAutoReloadSeconds']); } + + // Read Text fields + if ( isset ($_POST['PrependTitle']) ) { $content['PrependTitle'] = DB_RemoveBadChars($_POST['PrependTitle']); } + if ( isset ($_POST['SearchCustomButtonCaption']) ) { $content['SearchCustomButtonCaption'] = DB_RemoveBadChars($_POST['SearchCustomButtonCaption']); } + if ( isset ($_POST['SearchCustomButtonSearch']) ) { $content['SearchCustomButtonSearch'] = DB_RemoveBadChars($_POST['SearchCustomButtonSearch']); } + + // Save configuration variables now + WriteConfigValue( "ViewDefaultLanguage", true ); + WriteConfigValue( "ViewDefaultTheme", true ); + + WriteConfigValue( "ViewUseTodayYesterday", true ); + WriteConfigValue( "ViewEnableDetailPopups", true ); + WriteConfigValue( "EnableIPAddressResolve", true ); + WriteConfigValue( "MiscShowDebugMsg", true ); + WriteConfigValue( "MiscShowDebugGridCounter", true ); + WriteConfigValue( "MiscShowPageRenderStats", true ); + WriteConfigValue( "MiscEnableGzipCompression", true ); + WriteConfigValue( "DebugUserLogin", true ); + + WriteConfigValue( "ViewMessageCharacterLimit", true ); + WriteConfigValue( "ViewEntriesPerPage", true ); + WriteConfigValue( "ViewEnableAutoReloadSeconds", true ); + + WriteConfigValue( "PrependTitle", true ); + WriteConfigValue( "SearchCustomButtonCaption", true ); + WriteConfigValue( "SearchCustomButtonSearch", true ); + + // Do a redirect + RedirectResult( $content['LN_GEN_SUCCESSFULLYSAVED'], "index.php" ); } } -*/ -/* -// --- BEGIN CREATE TITLE -$content['TITLE'] = InitPageTitle(); -if ( $content['messageenabled'] == "true" ) -{ - // Append custom title part! - $content['TITLE'] .= " :: Details for '" . $content['uid_current'] . "'"; -} -else -{ - // APpend to title Page title - $content['TITLE'] .= " :: Unknown uid"; -} -// --- END CREATE TITLE -*/ +// Set checkbox States +if ($content['ViewUseTodayYesterday'] == 1) { $content['ViewUseTodayYesterday_checked'] = "checked"; } else { $content['ViewUseTodayYesterday_checked'] = ""; } +if ($content['ViewEnableDetailPopups'] == 1) { $content['ViewEnableDetailPopups_checked'] = "checked"; } else { $content['ViewEnableDetailPopups_checked'] = ""; } +if ($content['EnableIPAddressResolve'] == 1) { $content['EnableIPAddressResolve_checked'] = "checked"; } else { $content['EnableIPAddressResolve_checked'] = ""; } + +if ($content['MiscShowDebugMsg'] == 1) { $content['MiscShowDebugMsg_checked'] = "checked"; } else { $content['MiscShowDebugMsg_checked'] = ""; } +if ($content['MiscShowDebugGridCounter'] == 1) { $content['MiscShowDebugGridCounter_checked'] = "checked"; } else { $content['MiscShowDebugGridCounter_checked'] = ""; } +if ($content['MiscShowPageRenderStats'] == 1) { $content['MiscShowPageRenderStats_checked'] = "checked"; } else { $content['MiscShowPageRenderStats_checked'] = ""; } +if ($content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompression_checked'] = "checked"; } else { $content['MiscEnableGzipCompression_checked'] = ""; } +if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } + + +// --- // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); diff --git a/src/include/functions_db.php b/src/include/functions_db.php index a0f52e0..11abdb3 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -279,7 +279,7 @@ function DB_Exec($query) return false; } -function WriteConfigValue($szValue, $is_global = true) +function WriteConfigValue($szValue, $is_global = true, $userid = false, $groupid = false) { // --- Abort in this case! global $CFG, $content; @@ -287,18 +287,23 @@ function WriteConfigValue($szValue, $is_global = true) return; // --- - $result = DB_Query("SELECT name FROM " . STATS_CONFIG . " WHERE name = '" . $szValue . "' AND is_global = " . $is_global); + // TODO HANDLE USER AND GROUP FIELDS! + + // Also copy to $CFG array + $CFG[$szValue] = $content[$szValue]; + + $result = DB_Query("SELECT propname FROM " . DB_CONFIG . " WHERE propname = '" . $szValue . "' AND is_global = " . $is_global); $rows = DB_GetAllRows($result, true); if ( !isset($rows) ) { // New Entry - $result = DB_Query("INSERT INTO " . STATS_CONFIG . " (name, value, is_global) VALUES ( '" . $szValue . "', '" . $CFG[$szValue] . "', " . $is_global . ")"); + $result = DB_Query("INSERT INTO " . DB_CONFIG . " (propname, propvalue, is_global) VALUES ( '" . $szValue . "', '" . $CFG[$szValue] . "', " . $is_global . ")"); DB_FreeQuery($result); } else { // Update Entry - $result = DB_Query("UPDATE " . STATS_CONFIG . " SET value = '" . $CFG[$szValue] . "' WHERE name = '" . $szValue . "' AND is_global = " . $is_global); + $result = DB_Query("UPDATE " . DB_CONFIG . " SET propvalue = '" . $CFG[$szValue] . "' WHERE propname = '" . $szValue . "' AND is_global = " . $is_global); DB_FreeQuery($result); } } diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 300886e..899b155 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -62,7 +62,7 @@ $content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; $content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; $content['LN_GEN_CUSTBTNCAPT'] = "Custom search caption"; $content['LN_GEN_CUSTBTNSRCH'] = "Custom search string"; -$content['LN_GEN_'] = ""; +$content['LN_GEN_SUCCESSFULLYSAVED'] = "The configuration Values have been successfully saved"; $content['LN_GEN_'] = ""; // User Center From 2fabf173a91b38ec2fc427016edd4ddedb10341a Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 17 Jul 2008 14:50:19 +0200 Subject: [PATCH 014/142] Searches are loaded now from database as well. Changed minor things config functions --- src/admin/users.php | 2 +- src/include/functions_common.php | 84 ++--- src/include/functions_config.php | 507 ++++++++++++++++++------------- src/include/functions_users.php | 37 ++- 4 files changed, 352 insertions(+), 278 deletions(-) diff --git a/src/admin/users.php b/src/admin/users.php index 817074b..f1d81b5 100644 --- a/src/admin/users.php +++ b/src/admin/users.php @@ -58,7 +58,7 @@ IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); -if ($_GET['miniop'] == "setisadmin") +if ( isset($_GET['miniop']) && $_GET['miniop'] == "setisadmin" ) { if ( isset($_GET['id']) && isset($_GET['newval']) ) { diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 911e17e..d078bd1 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -107,57 +107,6 @@ function InitUserSystemPhpLogCon() } } -function InitPhpLogConConfigFile($bHandleMissing = true) -{ - // Needed to make global - global $CFG, $gl_root_path, $content; - - if ( file_exists($gl_root_path . 'config.php') && GetFileLength($gl_root_path . 'config.php') > 0 ) - { - // Include the main config - include_once($gl_root_path . 'config.php'); - - // Easier DB Access - define('DB_CONFIG', $CFG['UserDBPref'] . "config"); - define('DB_GROUPS', $CFG['UserDBPref'] . "groups"); - define('DB_GROUPMEMBERS', $CFG['UserDBPref'] . "groupmembers"); - define('DB_SEARCHES', $CFG['UserDBPref'] . "searches"); - define('DB_SOURCES', $CFG['UserDBPref'] . "sources"); - define('DB_USERS', $CFG['UserDBPref'] . "users"); - define('DB_VIEWS', $CFG['UserDBPref'] . "views"); - - // Legacy support for old columns definition format! - if ( isset($CFG['Columns']) && is_array($CFG['Columns']) ) - AppendLegacyColumns(); - - // --- Now Copy all entries into content variable - foreach ($CFG as $key => $value ) - $content[$key] = $value; - // --- - - // For MiscShowPageRenderStats - if ( $CFG['MiscShowPageRenderStats'] == 1 ) - { - $content['ShowPageRenderStats'] = "true"; - InitPageRenderStats(); - } - - // return result - return true; - } - else - { - // if handled ourselfe, we die in CheckForInstallPhp. - if ( $bHandleMissing == true ) - { - // Check for installscript! - CheckForInstallPhp(); - } - else - return false; - } -} - function CheckForInstallPhp() { // Check for installscript! @@ -545,22 +494,29 @@ function InitConfigurationValues() // Now we init the user session stuff InitUserSession(); - - if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + + // Check if user needs to be logged in + if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + { + if ( !$content['SESSION_LOGGEDIN'] ) { - if ( !$content['SESSION_LOGGEDIN'] ) - { - // User needs to be logged in, redirect to login page - if ( !defined("IS_LOGINPAGE") ) - RedirectToUserLogin(); - } + // User needs to be logged in, redirect to login page + if ( !defined("IS_LOGINPAGE") ) + RedirectToUserLogin(); } - else if ( defined('IS_ADMINPAGE') ) // Language System not initialized yet - DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); + } + else if ( defined('IS_ADMINPAGE') ) // Language System not initialized yet + DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); + + // Load Configured Searches + LoadSearchesFromDatabase(); + + // Load Configured Views +// LoadViewsFromDatabase(); + + // Load Configured Sources +// LoadSourcesFromDatabase(); - // General defaults -// // --- Language Handling -// if ( !isset($content['gen_lang']) ) { $content['gen_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; } // Database Version Checker! if ( $content['database_internalversion'] > $content['database_installedversion'] ) diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 5c73e44..92e86a9 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -1,246 +1,335 @@ www.phplogcon.org <- * +* ----------------------------------------------------------------- * +* Maintain and read Source Configurations * +* * +* -> Configuration need variables for the Database connection * +* +* Copyright (C) 2008 Adiscon GmbH. +* +* This file is part of phpLogCon. +* +* PhpLogCon is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* PhpLogCon is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with phpLogCon. If not, see . +* +* A copy of the GPL can be found in the file "COPYING" in this +* distribution. ********************************************************************* - * -> www.phplogcon.org <- * - * ----------------------------------------------------------------- * - * Maintain and read Source Configurations * - * * - * -> Configuration need variables for the Database connection * - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution. - ********************************************************************* - */ +*/ - // --- Avoid directly accessing this file! - if ( !defined('IN_PHPLOGCON') ) - { - die('Hacking attempt'); - exit; - } - // --- +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- - // --- Perform necessary includes - require_once($gl_root_path . 'classes/logstreamconfig.class.php'); - // --- +// --- Perform necessary includes +require_once($gl_root_path . 'classes/logstreamconfig.class.php'); +// --- - function InitSourceConfigs() - { - global $CFG, $content, $currentSourceID, $gl_root_path; +function InitSourceConfigs() +{ + global $CFG, $content, $currentSourceID, $gl_root_path; - // Init Source Configs! - if ( isset($CFG['Sources']) ) - { - $iCount = count($CFG['Sources']); - foreach( $CFG['Sources'] as &$mysource ) + // Init Source Configs! + if ( isset($CFG['Sources']) ) + { + $iCount = count($CFG['Sources']); + foreach( $CFG['Sources'] as &$mysource ) + { + if ( isset($mysource['SourceType']) ) { - if ( isset($mysource['SourceType']) ) - { - // Set Array Index, TODO: Check for invalid characters! - $iSourceID = $mysource['ID']; - // Copy general properties + // Set Array Index, TODO: Check for invalid characters! + $iSourceID = $mysource['ID']; + // Copy general properties // $content['Sources'][$iSourceID]['ID'] = $mysource['ID']; // $content['Sources'][$iSourceID]['Name'] = $mysource['Name']; // $content['Sources'][$iSourceID]['SourceType'] = $mysource['SourceType']; - - // Set default if not set! - if ( !isset($mysource['LogLineType']) ) - $content['Sources'][$iSourceID]['LogLineType'] = "syslog"; + + // Set default if not set! + if ( !isset($mysource['LogLineType']) ) + $content['Sources'][$iSourceID]['LogLineType'] = "syslog"; - // Set different view if necessary - if ( isset($_SESSION[$iSourceID . "-View"]) ) - { - // Overwrite configured view! - $content['Sources'][$iSourceID]['ViewID'] = $_SESSION[$iSourceID . "-View"]; - } + // Set different view if necessary + if ( isset($_SESSION[$iSourceID . "-View"]) ) + { + // Overwrite configured view! + $content['Sources'][$iSourceID]['ViewID'] = $_SESSION[$iSourceID . "-View"]; + } + else + { + if ( isset($mysource['ViewID']) ) + // Set to configured Source ViewID + $content['Sources'][$iSourceID]['ViewID'] = $mysource['ViewID']; else - { - if ( isset($mysource['ViewID']) ) - // Set to configured Source ViewID - $content['Sources'][$iSourceID]['ViewID'] = $mysource['ViewID']; - else - // Not configured, maybe old legacy cfg. Set default view. - $content['Sources'][$iSourceID]['ViewID'] = strlen($CFG['DefaultViewsID']) > 0 ? $CFG['DefaultViewsID'] : "SYSLOG"; + // Not configured, maybe old legacy cfg. Set default view. + $content['Sources'][$iSourceID]['ViewID'] = strlen($CFG['DefaultViewsID']) > 0 ? $CFG['DefaultViewsID'] : "SYSLOG"; - } + } - // Only for the display box - $content['Sources'][$iSourceID]['selected'] = ""; - - // Create Config instance! - if ( $mysource['SourceType'] == SOURCE_DISK ) - { - // Perform necessary include - require_once($gl_root_path . 'classes/logstreamconfigdisk.class.php'); + // Only for the display box + $content['Sources'][$iSourceID]['selected'] = ""; + + // Create Config instance! + if ( $mysource['SourceType'] == SOURCE_DISK ) + { + // Perform necessary include + require_once($gl_root_path . 'classes/logstreamconfigdisk.class.php'); - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDisk(); - $content['Sources'][$iSourceID]['ObjRef']->FileName = $mysource['DiskFile']; - $content['Sources'][$iSourceID]['ObjRef']->LineParserType = $mysource['LogLineType']; - } - else if ( $mysource['SourceType'] == SOURCE_DB ) - { - // Perform necessary include - require_once($gl_root_path . 'classes/logstreamconfigdb.class.php'); + $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDisk(); + $content['Sources'][$iSourceID]['ObjRef']->FileName = $mysource['DiskFile']; + $content['Sources'][$iSourceID]['ObjRef']->LineParserType = $mysource['LogLineType']; + } + else if ( $mysource['SourceType'] == SOURCE_DB ) + { + // Perform necessary include + require_once($gl_root_path . 'classes/logstreamconfigdb.class.php'); - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDB(); - $content['Sources'][$iSourceID]['ObjRef']->DBServer = $mysource['DBServer']; - $content['Sources'][$iSourceID]['ObjRef']->DBName = $mysource['DBName']; - // Workaround a little bug from the installer script - if ( isset($mysource['DBType']) ) - $content['Sources'][$iSourceID]['ObjRef']->DBType = $mysource['DBType']; - else - $content['Sources'][$iSourceID]['ObjRef']->DBType = DB_MYSQL; - - $content['Sources'][$iSourceID]['ObjRef']->DBTableName = $mysource['DBTableName']; - - // Legacy handling for tabletype! - if ( isset($mysource['DBTableType']) && strtolower($mysource['DBTableType']) == "winsyslog" ) - $content['Sources'][$iSourceID]['ObjRef']->DBTableType = "monitorware"; // Convert to MonitorWare! - else - $content['Sources'][$iSourceID]['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); - - // Optional parameters! - if ( isset($mysource['DBPort']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPort = $mysource['DBPort']; } - if ( isset($mysource['DBUser']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBUser = $mysource['DBUser']; } - if ( isset($mysource['DBPassword']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPassword = $mysource['DBPassword']; } - if ( isset($mysource['DBEnableRowCounting']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } - } - else if ( $mysource['SourceType'] == SOURCE_PDO ) - { - // Perform necessary include - require_once($gl_root_path . 'classes/logstreamconfigpdo.class.php'); - - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigPDO(); - $content['Sources'][$iSourceID]['ObjRef']->DBServer = $mysource['DBServer']; - $content['Sources'][$iSourceID]['ObjRef']->DBName = $mysource['DBName']; + $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDB(); + $content['Sources'][$iSourceID]['ObjRef']->DBServer = $mysource['DBServer']; + $content['Sources'][$iSourceID]['ObjRef']->DBName = $mysource['DBName']; + // Workaround a little bug from the installer script + if ( isset($mysource['DBType']) ) $content['Sources'][$iSourceID]['ObjRef']->DBType = $mysource['DBType']; - $content['Sources'][$iSourceID]['ObjRef']->DBTableName = $mysource['DBTableName']; + else + $content['Sources'][$iSourceID]['ObjRef']->DBType = DB_MYSQL; + + $content['Sources'][$iSourceID]['ObjRef']->DBTableName = $mysource['DBTableName']; + + // Legacy handling for tabletype! + if ( isset($mysource['DBTableType']) && strtolower($mysource['DBTableType']) == "winsyslog" ) + $content['Sources'][$iSourceID]['ObjRef']->DBTableType = "monitorware"; // Convert to MonitorWare! + else $content['Sources'][$iSourceID]['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); - // Optional parameters! - if ( isset($mysource['DBPort']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPort = $mysource['DBPort']; } - if ( isset($mysource['DBUser']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBUser = $mysource['DBUser']; } - if ( isset($mysource['DBPassword']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPassword = $mysource['DBPassword']; } - if ( isset($mysource['DBEnableRowCounting']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } - } - else - { - // UNKNOWN, remove config entry! - unset($content['Sources'][$iSourceID]); - - // TODO: Output CONFIG WARNING - die( "Not supported yet!" ); - } - - // Set generic configuration options - $content['Sources'][$iSourceID]['ObjRef']->_pageCount = $CFG['ViewEntriesPerPage']; - - // Set default SourceID here! - if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) - $currentSourceID = $iSourceID; + // Optional parameters! + if ( isset($mysource['DBPort']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPort = $mysource['DBPort']; } + if ( isset($mysource['DBUser']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBUser = $mysource['DBUser']; } + if ( isset($mysource['DBPassword']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPassword = $mysource['DBPassword']; } + if ( isset($mysource['DBEnableRowCounting']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } } + else if ( $mysource['SourceType'] == SOURCE_PDO ) + { + // Perform necessary include + require_once($gl_root_path . 'classes/logstreamconfigpdo.class.php'); + + $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigPDO(); + $content['Sources'][$iSourceID]['ObjRef']->DBServer = $mysource['DBServer']; + $content['Sources'][$iSourceID]['ObjRef']->DBName = $mysource['DBName']; + $content['Sources'][$iSourceID]['ObjRef']->DBType = $mysource['DBType']; + $content['Sources'][$iSourceID]['ObjRef']->DBTableName = $mysource['DBTableName']; + $content['Sources'][$iSourceID]['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); + + // Optional parameters! + if ( isset($mysource['DBPort']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPort = $mysource['DBPort']; } + if ( isset($mysource['DBUser']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBUser = $mysource['DBUser']; } + if ( isset($mysource['DBPassword']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPassword = $mysource['DBPassword']; } + if ( isset($mysource['DBEnableRowCounting']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } + } + else + { + // UNKNOWN, remove config entry! + unset($content['Sources'][$iSourceID]); + + // TODO: Output CONFIG WARNING + die( "Not supported yet!" ); + } + + // Set generic configuration options + $content['Sources'][$iSourceID]['ObjRef']->_pageCount = $CFG['ViewEntriesPerPage']; + + // Set default SourceID here! + if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) + $currentSourceID = $iSourceID; } } + } - // Read SourceID from GET Querystring - if ( isset($_GET['sourceid']) && isset($content['Sources'][$_GET['sourceid']]) ) - { - $currentSourceID = $_GET['sourceid']; - $_SESSION['currentSourceID'] = $currentSourceID; - } + // Read SourceID from GET Querystring + if ( isset($_GET['sourceid']) && isset($content['Sources'][$_GET['sourceid']]) ) + { + $currentSourceID = $_GET['sourceid']; + $_SESSION['currentSourceID'] = $currentSourceID; + } + else + { + // Set Source from session if available! + if ( isset($_SESSION['currentSourceID']) && isset($content['Sources'][$_SESSION['currentSourceID']]) ) + $currentSourceID = $_SESSION['currentSourceID']; else { - // Set Source from session if available! - if ( isset($_SESSION['currentSourceID']) && isset($content['Sources'][$_SESSION['currentSourceID']]) ) - $currentSourceID = $_SESSION['currentSourceID']; + if ( isset($CFG['DefaultSourceID']) && isset($content['Sources'][ $CFG['DefaultSourceID'] ]) ) + // Set Source to preconfigured sourceID! + $_SESSION['currentSourceID'] = $CFG['DefaultSourceID']; else - { - if ( isset($CFG['DefaultSourceID']) && isset($content['Sources'][ $CFG['DefaultSourceID'] ]) ) - // Set Source to preconfigured sourceID! - $_SESSION['currentSourceID'] = $CFG['DefaultSourceID']; - else - // No Source stored in session, then to so now! - $_SESSION['currentSourceID'] = $currentSourceID; - } + // No Source stored in session, then to so now! + $_SESSION['currentSourceID'] = $currentSourceID; + } + } + + // Set for the selection box in the header + $content['Sources'][$currentSourceID]['selected'] = "selected"; + + // --- Additional handling needed for the current view! + global $currentViewID; + $currentViewID = $content['Sources'][$currentSourceID]['ViewID']; + + // Set selected state for correct View, for selection box ^^ + $content['Views'][ $currentViewID ]['selected'] = "selected"; + + // If DEBUG Mode is enabled, we prepend the UID field into the col list! + if ( $CFG['MiscShowDebugMsg'] == 1 && isset($content['Views'][$currentViewID]) ) + array_unshift( $content['Views'][$currentViewID]['Columns'], SYSLOG_UID); + // --- +} + +/* +* This function Inits preconfigured Views. +*/ +function InitViewConfigs() +{ + global $CFG, $content, $currentViewID; + + // Predefined phpLogCon Views + $CFG['Views']['SYSLOG']= array( + 'ID' => "SYSLOG", + 'DisplayName' =>"Syslog Fields", + 'Columns' => array ( SYSLOG_DATE, SYSLOG_FACILITY, SYSLOG_SEVERITY, SYSLOG_HOST, SYSLOG_SYSLOGTAG, SYSLOG_PROCESSID, SYSLOG_MESSAGETYPE, SYSLOG_MESSAGE ), + ); + $CFG['Views']['EVTRPT']= array( + 'ID' => "EVTRPT", + 'DisplayName' =>"EventLog Fields", + 'Columns' => array ( SYSLOG_DATE, SYSLOG_HOST, SYSLOG_SEVERITY, SYSLOG_EVENT_LOGTYPE, SYSLOG_EVENT_SOURCE, SYSLOG_EVENT_ID, SYSLOG_EVENT_USER, SYSLOG_MESSAGE ), + ); + + // Set default of 'DefaultViewsID' + $CFG['DefaultViewsID'] = "SYSLOG"; + + // Loop through views now and copy into content array! + foreach ( $CFG['Views'] as $key => $view ) + $content['Views'][$key] = $view; +} + +/* +* This function Inits preconfigured Views. +*/ +function AppendLegacyColumns() +{ + global $CFG, $content; + + // Init View from legacy Columns + $CFG['Views']['LEGACY']= array( + 'ID' => "LEGACY", + 'DisplayName' =>"Legacy Columns Configuration", + 'Columns' => $CFG['Columns'], + ); + + // set default to legacy of no default view is specified! + if ( !isset($CFG['DefaultViewsID']) || strlen($CFG['DefaultViewsID']) <= 0 ) + $CFG['DefaultViewsID'] = "LEGACY"; +} + +function InitPhpLogConConfigFile($bHandleMissing = true) +{ + // Needed to make global + global $CFG, $gl_root_path, $content; + + if ( file_exists($gl_root_path . 'config.php') && GetFileLength($gl_root_path . 'config.php') > 0 ) + { + // Include the main config + include_once($gl_root_path . 'config.php'); + + // Easier DB Access + define('DB_CONFIG', $CFG['UserDBPref'] . "config"); + define('DB_GROUPS', $CFG['UserDBPref'] . "groups"); + define('DB_GROUPMEMBERS', $CFG['UserDBPref'] . "groupmembers"); + define('DB_SEARCHES', $CFG['UserDBPref'] . "searches"); + define('DB_SOURCES', $CFG['UserDBPref'] . "sources"); + define('DB_USERS', $CFG['UserDBPref'] . "users"); + define('DB_VIEWS', $CFG['UserDBPref'] . "views"); + + // Legacy support for old columns definition format! + if ( isset($CFG['Columns']) && is_array($CFG['Columns']) ) + AppendLegacyColumns(); + + // --- Now Copy all entries into content variable + foreach ($CFG as $key => $value ) + $content[$key] = $value; + // --- + + // For MiscShowPageRenderStats + if ( $CFG['MiscShowPageRenderStats'] == 1 ) + { + $content['ShowPageRenderStats'] = "true"; + InitPageRenderStats(); } - // Set for the selection box in the header - $content['Sources'][$currentSourceID]['selected'] = "selected"; - - // --- Additional handling needed for the current view! - global $currentViewID; - $currentViewID = $content['Sources'][$currentSourceID]['ViewID']; - - // Set selected state for correct View, for selection box ^^ - $content['Views'][ $currentViewID ]['selected'] = "selected"; - - // If DEBUG Mode is enabled, we prepend the UID field into the col list! - if ( $CFG['MiscShowDebugMsg'] == 1 && isset($content['Views'][$currentViewID]) ) - array_unshift( $content['Views'][$currentViewID]['Columns'], SYSLOG_UID); - // --- + // return result + return true; } - - /* - * This function Inits preconfigured Views. - */ - function InitViewConfigs() + else { - global $CFG, $content, $currentViewID; - - // Predefined phpLogCon Views - $CFG['Views']['SYSLOG']= array( - 'ID' => "SYSLOG", - 'DisplayName' =>"Syslog Fields", - 'Columns' => array ( SYSLOG_DATE, SYSLOG_FACILITY, SYSLOG_SEVERITY, SYSLOG_HOST, SYSLOG_SYSLOGTAG, SYSLOG_PROCESSID, SYSLOG_MESSAGETYPE, SYSLOG_MESSAGE ), - ); - $CFG['Views']['EVTRPT']= array( - 'ID' => "EVTRPT", - 'DisplayName' =>"EventLog Fields", - 'Columns' => array ( SYSLOG_DATE, SYSLOG_HOST, SYSLOG_SEVERITY, SYSLOG_EVENT_LOGTYPE, SYSLOG_EVENT_SOURCE, SYSLOG_EVENT_ID, SYSLOG_EVENT_USER, SYSLOG_MESSAGE ), - ); - - // Set default of 'DefaultViewsID' - $CFG['DefaultViewsID'] = "SYSLOG"; - - // Loop through views now and copy into content array! - foreach ( $CFG['Views'] as $key => $view ) - $content['Views'][$key] = $view; + // if handled ourselfe, we die in CheckForInstallPhp. + if ( $bHandleMissing == true ) + { + // Check for installscript! + CheckForInstallPhp(); + } + else + return false; } +} - /* - * This function Inits preconfigured Views. - */ - function AppendLegacyColumns() +/* +* Helper function to load configured Searches from the database +*/ +function LoadSearchesFromDatabase() +{ + // Needed to make global + global $CFG, $content; + + // --- Create SQL Query + + // Create Where for USERID + if ( isset($content['SESSION_LOGGEDIN']) && $content['SESSION_LOGGEDIN'] ) + $szWhereUser = " OR " . DB_SEARCHES . ".userid = " . $content['SESSION_USERID']; + else + $szWhereUser = ""; + + if ( isset($content['SESSION_GROUPIDS']) ) + $szGroupWhere = " OR " . DB_SEARCHES . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ") "; + else + $szGroupWhere = ""; + + $sqlquery = " SELECT * " . + " FROM " . DB_SEARCHES . + " WHERE userid = NULL " . + $szWhereUser . + $szGroupWhere . + " ORDER BY " . DB_SEARCHES . ".DisplayName"; + + $result = DB_Query($sqlquery); + $myrows = DB_GetAllRows($result, true); + if ( isset($myrows ) && count($myrows) > 0 ) { - global $CFG, $content; + // Cleanup searches and fill / load from database + - // Init View from legacy Columns - $CFG['Views']['LEGACY']= array( - 'ID' => "LEGACY", - 'DisplayName' =>"Legacy Columns Configuration", - 'Columns' => $CFG['Columns'], - ); - - // set default to legacy of no default view is specified! - if ( !isset($CFG['DefaultViewsID']) || strlen($CFG['DefaultViewsID']) <= 0 ) - $CFG['DefaultViewsID'] = "LEGACY"; } +} ?> \ No newline at end of file diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 01a1510..2dc8a1b 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -65,10 +65,13 @@ function InitUserSession() } else { + // Copy variables from session! $content['SESSION_LOGGEDIN'] = true; $content['SESSION_USERNAME'] = $_SESSION['SESSION_USERNAME']; $content['SESSION_USERID'] = $_SESSION['SESSION_USERID']; $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; + if ( isset($_SESSION['SESSION_GROUPIDS']) ) + $content['SESSION_GROUPIDS'] = $_SESSION['SESSION_GROUPIDS']; // Successfully logged in return true; @@ -122,11 +125,11 @@ function CheckUserLogin( $username, $password ) // TODO: SessionTime and AccessLevel check $md5pass = md5($password); - $sqlselect = "SELECT * FROM " . DB_USERS . " WHERE username = '" . $username . "' and password = '" . $md5pass . "'"; - $result = DB_Query($sqlselect); + $sqlquery = "SELECT * FROM " . DB_USERS . " WHERE username = '" . $username . "' and password = '" . $md5pass . "'"; + $result = DB_Query($sqlquery); $myrow = DB_GetSingleRow($result, true); - + // The admin field must be set! if ( isset($myrow['is_admin']) ) { $_SESSION['SESSION_LOGGEDIN'] = true; @@ -139,7 +142,33 @@ function CheckUserLogin( $username, $password ) $content['SESSION_USERID'] = $_SESSION['SESSION_USERID']; $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; - // TODO SET LAST LOGIN TIME! + // --- Read Groupmember ship for the user! + $sqlquery = "SELECT " . + DB_GROUPMEMBERS . ".groupid, " . + DB_GROUPMEMBERS . ".is_member " . + "FROM " . DB_GROUPMEMBERS . " WHERE userid = " . $content['SESSION_USERID'] . " AND " . DB_GROUPMEMBERS . ".is_member = 1"; + $result = DB_Query($sqlquery); + $myrows = DB_GetAllRows($result, true); + if ( isset($myrows ) && count($myrows) > 0 ) + { + for($i = 0; $i < count($myrows); $i++) + { + if ( isset($content['SESSION_GROUPIDS']) ) + $content['SESSION_GROUPIDS'] .= ", " . $myrows[$i]['groupid']; + else + $content['SESSION_GROUPIDS'] .= $myrows[$i]['groupid']; + } + } + + // Copy into session as well + $_SESSION['SESSION_GROUPIDS'] = $content['SESSION_GROUPIDS']; + // --- + + + // ---Set LASTLOGIN Time! + $result = DB_Query("UPDATE " . DB_USERS . " SET last_login = " . time() . " WHERE ID = " . $content['SESSION_USERID']); + DB_FreeQuery($result); + // --- // Success ! return true; From 70c941dd640e847d7fa3dad4bb1ee2fd97137e74 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 17 Jul 2008 17:29:35 +0200 Subject: [PATCH 015/142] Added Custom Search Admin page and logic to load the custom searches from the database --- src/admin/groups.php | 2 +- src/admin/index.php | 2 +- src/admin/searches.php | 362 ++++++++++++++++++++++++ src/admin/users.php | 2 +- src/images/icons/earth.png | Bin 0 -> 990 bytes src/include/functions_common.php | 1 + src/include/functions_config.php | 14 +- src/lang/en/admin.php | 33 ++- src/templates/admin/admin_groups.html | 3 + src/templates/admin/admin_searches.html | 93 ++++++ src/templates/admin/admin_users.html | 1 + 11 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 src/admin/searches.php create mode 100644 src/images/icons/earth.png create mode 100644 src/templates/admin/admin_searches.html diff --git a/src/admin/groups.php b/src/admin/groups.php index 2858456..b733750 100644 --- a/src/admin/groups.php +++ b/src/admin/groups.php @@ -482,7 +482,7 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: Group Options"; +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_GROUPOPT']; // --- END CREATE TITLE // --- Parsen and Output diff --git a/src/admin/index.php b/src/admin/index.php index ba285ba..33507f2 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -141,7 +141,7 @@ if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "che // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: General Options"; +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_GENOPT']; // --- END CREATE TITLE // --- Parsen and Output diff --git a/src/admin/searches.php b/src/admin/searches.php new file mode 100644 index 0000000..3b4ce41 --- /dev/null +++ b/src/admin/searches.php @@ -0,0 +1,362 @@ + Helps administrating custom searches + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +// Only if the user is an admin! +//if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) +// DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWSEARCH'] = "true"; + $content['SEARCH_FORMACTION'] = "addnewsearch"; + $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_ADD']; + + //PreInit these values + $content['DisplayName'] = ""; + $content['SearchQuery'] = ""; + $content['userid'] = null; + $content['CHECKED_ISUSERONLY'] = ""; + $content['SEARCHID'] = ""; + + // --- Check if groups are available + $sqlquery = "SELECT " . + DB_GROUPS . ".ID as mygroupid, " . + DB_GROUPS . ".groupname " . + "FROM " . DB_GROUPS . + " ORDER BY " . DB_GROUPS . ".groupname"; + $result = DB_Query($sqlquery); + $content['SUBGROUPS'] = DB_GetAllRows($result, true); + if ( isset($content['SUBGROUPS']) && count($content['SUBGROUPS']) > 0 ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + $content['SUBGROUPS'][$i]['group_selected'] = ""; + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWSEARCH'] = "true"; + $content['SEARCH_FORMACTION'] = "editsearch"; + $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT * " . + " FROM " . DB_SEARCHES . + " WHERE ID = " . $content['SEARCHID']; + + $result = DB_Query($sqlquery); + $mysearch = DB_GetSingleRow($result, true); + if ( isset($mysearch['DisplayName']) ) + { + $content['SEARCHID'] = $mysearch['ID']; + $content['DisplayName'] = $mysearch['DisplayName']; + $content['SearchQuery'] = $mysearch['SearchQuery']; + if ( $mysearch['userid'] != null ) + $content['CHECKED_ISUSERONLY'] = "checked"; + else + $content['CHECKED_ISUSERONLY'] = ""; + + // --- Check if groups are available + $sqlquery = "SELECT " . + DB_GROUPS . ".ID as mygroupid, " . + DB_GROUPS . ".groupname " . + "FROM " . DB_GROUPS . + " ORDER BY " . DB_GROUPS . ".groupname"; + $result = DB_Query($sqlquery); + $content['SUBGROUPS'] = DB_GetAllRows($result, true); + if ( isset($content['SUBGROUPS']) && count($content['SUBGROUPS']) > 0 ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else + { + $content['ISEDITORNEWSEARCH'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + } + else + { + $content['ISEDITORNEWSEARCH'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); + + // Get UserInfo + $result = DB_Query("SELECT DisplayName FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['DisplayName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SEARCH_WARNDELETESEARCH'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_DELSEARCH'], $content['SEARCHID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "searches.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + } + } +} + +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['SEARCHID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SEARCHID'] = -1; } + if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } + if ( isset ($_POST['SearchQuery']) ) { $content['SearchQuery'] = DB_RemoveBadChars($_POST['SearchQuery']); } else {$content['SearchQuery'] = ""; } + + // User & Group handeled specially + if ( isset ($_POST['isuseronly']) ) + { + $content['userid'] = $content['SESSION_USERID']; + $content['groupid'] = "null"; // Either user or group not both! + } + else + { + $content['userid'] = "null"; + if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) + $content['groupid'] = intval($_POST['groupid']); + else + $content['groupid'] = "null"; + } + + // --- Check mandotary values + if ( $content['DisplayName'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY']; + } + else if ( $content['SearchQuery'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY']; + } + // --- + + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewsearch" ) + { + // Add custom search now! + $sqlquery = "INSERT INTO " . DB_SEARCHES . " (DisplayName, SearchQuery, userid, groupid) + VALUES ('" . $content['DisplayName'] . "', + '" . $content['SearchQuery'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENADDED'], $content['DisplayName'] ) , "searches.php" ); + } + else if ( $_POST['op'] == "editsearch" ) + { + $result = DB_Query("SELECT ID FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + else + { + // Edit the Search Entry now! + $result = DB_Query("UPDATE " . DB_SEARCHES . " SET + DisplayName = '" . $content['DisplayName'] . "', + SearchQuery = '" . $content['SearchQuery'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SEARCHID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENEDIT'], $content['DisplayName']) , "searches.php" ); + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Searches + $content['LISTSEARCHES'] = "true"; + + // Read all Serverentries + $sqlquery = "SELECT " . + DB_SEARCHES . ".ID, " . + DB_SEARCHES . ".DisplayName, " . + DB_SEARCHES . ".SearchQuery, " . + DB_SEARCHES . ".userid, " . + DB_SEARCHES . ".groupid, " . + DB_USERS . ".username, " . + DB_GROUPS . ".groupname " . + " FROM " . DB_SEARCHES . + " LEFT OUTER JOIN (" . DB_USERS . ", " . DB_GROUPS . + ") ON (" . + DB_SEARCHES . ".userid=" . DB_USERS . ".ID AND " . + DB_SEARCHES . ".groupid=" . DB_GROUPS . ".ID " . + ") " . + " ORDER BY " . DB_SEARCHES . ".userid, " . DB_SEARCHES . ".groupid, " . DB_SEARCHES . ".DisplayName"; +//echo $sqlquery; + $result = DB_Query($sqlquery); + $content['SEARCHES'] = DB_GetAllRows($result, true); + + // --- Process Users + for($i = 0; $i < count($content['SEARCHES']); $i++) + { + $content['SEARCHES'][$i]['SearchQuery_Display'] = strlen($content['SEARCHES'][$i]['SearchQuery']) > 25 ? substr($content['SEARCHES'][$i]['SearchQuery'], 0, 25) . " ..." : $content['SEARCHES'][$i]['SearchQuery']; + + // --- Set Image for Type + if ( $content['SEARCHES'][$i]['userid'] != null ) + { + $content['SEARCHES'][$i]['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; + $content['SEARCHES'][$i]['SearchTypeText'] = $content["LN_GEN_USERONLY"]; + } + else if ( $content['SEARCHES'][$i]['groupid'] != null ) + { + $content['SEARCHES'][$i]['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; + $content['SEARCHES'][$i]['SearchTypeText'] = $content["LN_GEN_GROUPONLY"]; + } + else + { + $content['SEARCHES'][$i]['SearchTypeImage'] = $content["MENU_GLOBAL"]; + $content['SEARCHES'][$i]['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $content['SEARCHES'][$i]['cssclass'] = "line1"; + else + $content['SEARCHES'][$i]['cssclass'] = "line2"; + // --- + } + // --- +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_SEARCHOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_searches.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/admin/users.php b/src/admin/users.php index f1d81b5..09331e4 100644 --- a/src/admin/users.php +++ b/src/admin/users.php @@ -371,7 +371,7 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: User Options"; +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_USEROPT']; // --- END CREATE TITLE // --- Parsen and Output diff --git a/src/images/icons/earth.png b/src/images/icons/earth.png new file mode 100644 index 0000000000000000000000000000000000000000..a5517db6de75e55c3708880210b66b37274a761b GIT binary patch literal 990 zcmV<410np0P)WdKcSATc)}L3L*!GB7YTATcpIG&MRgF(4~2F)%Pb%&Rv5000McNliru z)&&<0F*|Y-$N>NV010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00TBj zL_t(|+D()1Z&Otm$G^QldP}?NT}#(3WNc-iOj#ggVnQ%c6ExYzcw=G+!B>VDHU0-C zz9zmhA;xHU0lXj(AV#u?8rejPQAUk03KQ6m(#`hXt-WpUuXBI-Tx^rx{gq1j-nFG}lV=9MS@zKj4?n!p z;I77ZMc>EJ;7{n?QbEjo50=Z&@S{-dWSsTRPYejJDz)XCPe^8lCj7-eo0&P~^pB0` z*NHy!JVwMfkme7e8CitvOrT~3V9~2!{2er-m-w9Y&H?4&{9nFZTPFjXE3Q{RUgu7w z)JHd9_MC1AI}5T&!LZ=Or=LAj*xgeAqq?Gm7|)r7tG2+ zc2B|&1UNp>WDKh5z;JCev`N^|fZ;b$;{t4bfgi6FFiHkb13DRBc*moE#*^8oc z3h~km)Ru=*(?LadV0#e+EC)ydoa!V**%~HsNRQC4TzH0u=L7+s>%!+4ipnB38iS}B z7HaetmJJ9yrs0;xAlBc4v-vz}zmlIF!BjJkm6SRrLP9?!M&vmxtYzr-O*)2+y5#|e z2-l$S>NK{>d9Fs(ed!aMEE3a7AHmc!@EwiLOldYqXc7jat!AM-vIwk(XKj)so-=MQBTKsED5Qy+bdkSbs5%b ziNwDW<**X{O7Xi#S@+$|{(-Py?7aoJ1S}B zhFSA$An$xFojWv@dr8_a2`>*Z$AhA7tSsLzJaE^4Zo)IyDDD<Qm;aR2}S literal 0 HcmV?d00001 diff --git a/src/include/functions_common.php b/src/include/functions_common.php index d078bd1..648d5f5 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -431,6 +431,7 @@ function InitFrontEndVariables() $content['MENU_ADD'] = $content['BASEPATH'] . "images/icons/add.png"; $content['MENU_EDIT'] = $content['BASEPATH'] . "images/icons/edit.png"; $content['MENU_DELETE'] = $content['BASEPATH'] . "images/icons/delete.png"; + $content['MENU_GLOBAL'] = $content['BASEPATH'] . "images/icons/earth.png"; $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 92e86a9..7a308be 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -306,26 +306,30 @@ function LoadSearchesFromDatabase() // Create Where for USERID if ( isset($content['SESSION_LOGGEDIN']) && $content['SESSION_LOGGEDIN'] ) - $szWhereUser = " OR " . DB_SEARCHES . ".userid = " . $content['SESSION_USERID']; + $szWhereUser = " OR " . DB_SEARCHES . ".userid = " . $content['SESSION_USERID'] . " "; else $szWhereUser = ""; if ( isset($content['SESSION_GROUPIDS']) ) - $szGroupWhere = " OR " . DB_SEARCHES . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ") "; + $szGroupWhere = " OR " . DB_SEARCHES . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ")"; else $szGroupWhere = ""; $sqlquery = " SELECT * " . " FROM " . DB_SEARCHES . - " WHERE userid = NULL " . + " WHERE (" . DB_SEARCHES . ".userid IS NULL AND " . DB_SEARCHES . ".groupid IS NULL) " . $szWhereUser . $szGroupWhere . - " ORDER BY " . DB_SEARCHES . ".DisplayName"; - + " ORDER BY " . DB_SEARCHES . ".userid, " . DB_SEARCHES . ".groupid, " . DB_SEARCHES . ".DisplayName"; +// " ORDER BY " . DB_SEARCHES . ".DisplayName"; $result = DB_Query($sqlquery); $myrows = DB_GetAllRows($result, true); if ( isset($myrows ) && count($myrows) > 0 ) { + // Overwrite Search Array with Database one + $CFG['Search'] = $myrows; + $content['Search'] = $myrows; + // Cleanup searches and fill / load from database diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 899b155..38f581e 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -42,6 +42,9 @@ $content['LN_DELETEYES'] = "Yes"; $content['LN_DELETENO'] = "No"; $content['LN_GEN_ACTIONS'] = "Available Actions"; $content['LN_ADMIN_SEND'] = "Send changes"; +$content['LN_GEN_USERONLY'] = "User only"; +$content['LN_GEN_GROUPONLY'] = "Group only"; +$content['LN_GEN_GLOBAL'] = "Global"; // General Options $content['LN_ADMIN_MISC'] = "Miscellaneous Options"; @@ -78,7 +81,7 @@ $content['LN_USER_ERROR_IDNOTFOUND'] = "Error, User with ID '%1' , was not found $content['LN_USER_ERROR_DONOTDELURSLF'] = "Error, you can not DELETE YOURSELF!"; $content['LN_USER_ERROR_DELUSER'] = "Deleting of the user with id '%1' failed!"; $content['LN_USER_ERROR_INVALIDID'] = "Error, invalid ID, User not found"; -$content['LN_USER_ERROR_HASBEENDEL'] = "The User '%1' has been successfully DELETED!"; +$content['LN_USER_ERROR_HASBEENDEL'] = "The User '%1' has been successfully deleted!"; $content['LN_USER_ERROR_USEREMPTY'] = "Error, Username was empty"; $content['LN_USER_ERROR_USERNAMETAKEN'] = "Error, this Username is already taken!"; $content['LN_USER_ERROR_PASSSHORT'] = "Error, Password was to short, or did not match"; @@ -110,7 +113,7 @@ $content['LN_GROUP_ERROR_HASBEENEDIT'] = "The group '%1' has been successfully e $content['LN_GROUP_ERROR_INVALIDGROUP'] = "Error, invalid ID, Group not found"; $content['LN_GROUP_WARNDELETEGROUP'] = "Are you sure that you want to delete the Group '%1'? All Groupsettings will be deleted as well."; $content['LN_GROUP_ERROR_DELGROUP'] = "Deleting of the group with id '%1' failed!"; -$content['LN_GROUP_ERROR_HASBEENDEL'] = "The Group '%1' has been successfully DELETED!"; +$content['LN_GROUP_ERROR_HASBEENDEL'] = "The Group '%1' has been successfully deleted!"; $content['LN_GROUP_MEMBERS'] = "Groupmembers: "; $content['LN_GROUP_ADDUSER'] = "Add User to Group"; $content['LN_GROUP_ERROR_USERIDMISSING'] = "The userid is missing."; @@ -123,4 +126,30 @@ $content['LN_GROUP_ERROR_REMUSERFROMGROUP'] = "The user '%1' could not be remove $content['LN_GROUP_USERHASBEENREMOVED'] = "The user '%1' has been successfully removed from the group '%2'"; $content['LN_GROUP_'] = ""; +// Custom Searches center +$content['LN_SEARCH_CENTER'] = "Custom Searches"; +$content['LN_SEARCH_ADD'] = "Add Custom Search"; +$content['LN_SEARCH_ID'] = "ID"; +$content['LN_SEARCH_NAME'] = "Search Name"; +$content['LN_SEARCH_QUERY'] = "Search Query"; +$content['LN_SEARCH_TYPE'] = "Type of Search"; +$content['LN_SEARCH_EDIT'] = "Edit Custom Search"; +$content['LN_SEARCH_DELETE'] = "Delete Custom Search"; +$content['LN_SEARCH_ADDEDIT'] = "Add / Edit a Custom Search"; +$content['LN_SEARCH_USERONLY'] = "For me only
(Only available to your user)"; +$content['LN_SEARCH_GROUPONLY'] = "For this group
(Only available to the selected group)"; +$content['LN_SEARCH_SELGROUPENABLE'] = ">> Select Group to enable <<"; +$content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; +$content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY'] = "The SearchQuery cannot be empty."; +$content['LN_SEARCH_HASBEENADDED'] = "The Custom Search '%1' has been successfully added."; +$content['LN_SEARCH_ERROR_IDNOTFOUND'] = "Could not find a search with ID '%1'."; +$content['LN_SEARCH_ERROR_INVALIDID'] = "Invalid search ID."; +$content['LN_SEARCH_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; +$content['LN_SEARCH_WARNDELETESEARCH'] = "Are you sure that you want to delete the Custom Search '%1'? This cannot be undone!"; +$content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id '%1' failed!"; +$content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; +$content['LN_SEARCH_'] = ""; +$content['LN_SEARCH_'] = ""; + + ?> \ No newline at end of file diff --git a/src/templates/admin/admin_groups.html b/src/templates/admin/admin_groups.html index 653f9bd..5004c9f 100644 --- a/src/templates/admin/admin_groups.html +++ b/src/templates/admin/admin_groups.html @@ -66,6 +66,7 @@ + @@ -93,6 +94,7 @@
{LN_GROUP_ADDEDIT}
{LN_GROUP_NAME}
+ + + + +
{LN_GROUP_ADDUSER}: '{GROUPNAME}'
{LN_USER_NAME} @@ -122,6 +124,7 @@ + - + - - + + - + - diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index 7a6e2e2..c6c6e6f 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -49,8 +49,8 @@ - - + + diff --git a/src/templates/admin/admin_views.html b/src/templates/admin/admin_views.html index 37097b2..61276a1 100644 --- a/src/templates/admin/admin_views.html +++ b/src/templates/admin/admin_views.html @@ -19,18 +19,25 @@ - - - + + + - + - + + From 4f6f0ed11e2b8b5026aeea9f3de8c256737edbca Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 14:45:45 +0200 Subject: [PATCH 118/142] Changed help link, again - also if local help is available, phpLogCon will link to this first --- src/include/functions_common.php | 9 ++++++++- src/templates/include_menu.html | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 94ad1d3..f4914fc 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -464,7 +464,7 @@ function CheckAndSetRunMode() function InitRuntimeInformations() { - global $content; + global $gl_root_path, $content; // Enable GZIP Compression if enabled! if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false && GetConfigSetting("MiscEnableGzipCompression", 1, CFGLEVEL_USER) == 1 ) @@ -475,6 +475,13 @@ function InitRuntimeInformations() } else $content['GzipCompressionEnmabled'] = "no"; + + // --- Check and Set manual link + if ( is_dir($gl_root_path . "doc") ) + $content['PHPLOGCON_HELPLINK'] = $content['BASEPATH'] . "doc"; + else + $content['PHPLOGCON_HELPLINK'] = "http://www.phplogcon.org/doc"; + // --- } function CreateDebugModes() diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index 59486f5..9fe6e40 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -8,7 +8,7 @@ - + From 096086f9f43c2e2e0d520a0f704b5d3c95479c4a Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 17 Sep 2008 14:47:03 +0200 Subject: [PATCH 119/142] begung working on doc set. Some basic information on where to find what added. Now ready to add actual project "doc meat" ;) --- doc/{ => devel}/Activity.dia | Bin doc/{ => devel}/objectmodel.dia | Bin doc/free_support.html | 54 ++++++++++++++ doc/manual.html | 124 +++++++++++--------------------- doc/professional_services.html | 60 ++++++++++++++++ doc/troubleshoot.html | 43 +++++++++++ 6 files changed, 197 insertions(+), 84 deletions(-) rename doc/{ => devel}/Activity.dia (100%) rename doc/{ => devel}/objectmodel.dia (100%) create mode 100644 doc/free_support.html create mode 100644 doc/professional_services.html create mode 100644 doc/troubleshoot.html diff --git a/doc/Activity.dia b/doc/devel/Activity.dia similarity index 100% rename from doc/Activity.dia rename to doc/devel/Activity.dia diff --git a/doc/objectmodel.dia b/doc/devel/objectmodel.dia similarity index 100% rename from doc/objectmodel.dia rename to doc/devel/objectmodel.dia diff --git a/doc/free_support.html b/doc/free_support.html new file mode 100644 index 0000000..7cbc4ae --- /dev/null +++ b/doc/free_support.html @@ -0,0 +1,54 @@ + + +Free Support for phpLogCon + + + +

Free Services for phpLogCon

+

A personal word from the authors of phpLogCon: +

The phpLogCon community provides ample free support resources. Please see our +troubleshooting guide to get started.

+

Every now and then we receive private mail with support questions. We appreciate +all feedback, but we must limit my resources so that we can help drive a great project +forward. +

To do so, we have decided not to reply to unsolicited support emails, at least not +with a solution (but rather a link to this page ;)). We hope this does not offend you. The +reason is quite simple: If we do personal support, you gain some advantage without +contributing something back. Think about it: if you ask your question on the public +forum or mailing list, others with the same problem can see it and, most importantly, even +years later find your post (and the answer) and get the problem solved. So by +solving your issue in public, you help create a great community ressource and also +help your fellow users finding solutions quicker. In the long term, this +also contributes to improved code because the more questions users can find +solutions to themselves, the fewer we need to look at. +

But it comes even better: the phpLogCon community is much broader than the authors ;) - there +are helpful other members hanging around at the public places. They often answer +questions, so that we do not need to look at them (btw, once again a big "thank you", folks!). +And, more important, those folks have different background than us. So they often +either know better how to solve your problem (e.g. because it is distro-specific) +or they know how to better phrase it. +So you do yourself a favor if you use the public places. +

An excellent place to go to is the +phpLogCon forum inside the +knowledge base (which in itself is a great place to visit!). For those used to +mailing lists, the +phpLogCon mailing list +also offers excellent advise. +

Don't like to post your question in a public place? Well, then you should +consider purchasing phpLogCon professional support. +The fees are very low and help fund the project. If you use phpLogCon seriously inside +a corporate environment, there is no excuse for not getting one of the support +packages ;) +

Of course, things are different when we ask you to mail us privately. We'll usually do +that when we think it makes sense, for example when we exchange debug logs. +

I hope you now understand the free support options and the reasoning for them. +I hope we haven't offended you with our words - this is not our intension. We just needed to +make clear why there are some limits on our responsiveness. Happy analyzing! +

[manual index] [phpLogCon site]

+

This documentation is part of the +phpLogCon +project.
+Copyright © 2008 by +Adiscon. +Released under the GNU GPL version 3 or higher.

+ diff --git a/doc/manual.html b/doc/manual.html index 91e7d0e..d4efa78 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1,100 +1,56 @@ -rsyslog documentation +phpLogCon documentation -

RSyslog - Documentation

-

Rsyslog -is an enhanced syslogd -supporting, among others, MySQL, -PostgreSQL, failover -log destinations, syslog/tcp, fine grain output format -control, high precision timestamps, queued operations and the ability to filter on any message -part. -It is quite compatible to stock sysklogd and can be used as a drop-in -replacement. Its -advanced features make it suitable for enterprise-class, encryption protected syslog -relay chains while at the same time being very easy to setup for the -novice user. And as we know what enterprise users really need, there is -also professional -rsyslog support available directly from the source!

-

This documentation is for version 3.21.5 (devel branch) of rsyslog. -Visit the rsyslog status page to obtain current -version information and project status. -

If you like rsyslog, you might -want to lend us a helping hand. It doesn't require a lot of -time - even a single mouse click helps. Learn how to help the rsyslog project. -Due to popular demand, there is now a side-by-side comparison -between rsyslog and syslog-ng.

-

If you are upgrading from rsyslog v2 or stock sysklogd, -be -sure to read the rsyslog v3 compatibility document! It will work even -if you do not read the doc, but doing so will definitely improve your experience.

-

Follow -the links below for the

    - -
  • troubleshooting rsyslog problems
  • -
  • configuration file syntax (rsyslog.conf)
  • -
  • property replacer, an important core component
  • -
  • a commented sample rsyslog.conf
  • -
  • rsyslog bug list
  • -
  • rsyslog packages
  • -
  • backgrounder on -generic syslog application design
  • -
  • description of rsyslog modules
  • -
  • rsyslogd man page (heavily outdated)
  • +

    phpLogCon - Documentation

    +

    The phpLogCon +project provides an easy to use but powerful front end for +searching, reviewing and analyzing network event data, including +syslog, windows event log and many other event sources. +It focusses on the user-interface side of this project, so the +data itself needs to be gathered by another program, +for example the stock syslogd, +rsyslog (often the distro's default syslogd), +WinSyslog or +MonitorWare Agent. +PhpLogCon works equally well on Linux and +Windows. It is a free, GPLed open source application written mostly in php. +Data can be obtained from databases but also from plain text files, for +example those that are written by the syslgod. +

    Follow the links below for the

    + +

    You can also browse the following online resources:

    -

    And don't forget about the rsyslog -mailing list. If you are interested in the "backstage", you -may find +

    And don't forget about the +phplogcon +mailing list. If you are interested in the "backstage" behind the overall idea that +phpLogCon contributes to, you may find Rainer's -blog an -interesting read (filter on syslog and rsyslog tags). -If you would like to use rsyslog source code inside your open source project, you can do that without -any restriction as long as your license is GPLv3 compatible. If your license is incompatible to GPLv3, -you may even be still permitted to use rsyslog source code. However, then you need to look at the way -rsyslog is licensed.

    +blog an interesting read.

    Feedback is always welcome, but if you have a support question, please do not -mail Rainer directly (why not?). +mail the authors directly (why not?). diff --git a/doc/professional_services.html b/doc/professional_services.html new file mode 100644 index 0000000..8b10f24 --- /dev/null +++ b/doc/professional_services.html @@ -0,0 +1,60 @@ + + + + +

    Professional Services for phpLogCon

    +

    Professional services are being offered by Adiscon, the company +that sponsors phpLogCon +development. For details, please contact +Adiscon Sales.

    + +

    EMail Support Service

    +Price: 99.00 EURO
    +Duration: 180 days +
    +Support level: 8x5 +

    Purchase phpLogCon support directly from the source. This +contract provides priority email support. It is a great option if you +need to provide proof of software support in your organization. This +contract provides

    +
      +
    • unlimited email support tickets during validity
    • +
    • fixes for current and past phpLogCon releases
    • +
    • advise on how to implement phpLogCon in the best possible way.
    • +
    +

    Under this contract, fixes for old phpLogCon releases will be +provided / created, assuming it is possible to do that with the +code base in question. Phone support is not included.

    +

    Custom Development

    +

    Do you need an exotic feature that otherwise would not be implemented? +Do you need something really quick, quicker than it is available via +the regular development schedule? Then, you may consider funding +development for a specific functionality. We are always looking for +interesting projects. If you hire us to to do the job, you can be sure +to get the best possible and probably quickest solution, because we are +obviously at the heart of the source code. No need to get aquainted to +anything, no risk of misunderstanding core concepts. Benefit from +our vast logging and auditing experience.

    +

    Please note that custom development is not limited to phpLogCon. We offer +a number of logging solutions and can also work as part of your time +for specific requirements. The opportunities are endless, just ask. We +will work with you on your requirements and provide a quote on the +estimated cost. Just write to sales@adiscon.com for details.

    Consulting Services

    +

    Do you have demanding logging requirements? Why not talk to a +real logging professional? Instead of trying to find the solution +like a needle in the haystack, talk to the team that brought rsyslog, +phpLogCon, the Windows MonitorWare products and other logging +solutions. We sweat logging for over 15 years now and can help quickly. +Depending on your needs, consulting can be carried out via email, the +phone or on your premises (for larger or local projects). Everything is +possible, it just depends on your needs. Consulting services are +available in English and German. Just mail sales@adiscon.com what you are interested in and we will work with you on a proposal that fits your needs. +

    +

    All agreements are governed under German law.

    + +

    [manual index] [phpLogCon site]

    +

    This documentation is part of the +phpLogCon project.
    +Copyright © 2008 by Adiscon. +Released under the GNU GPL version 3 or higher.

    + diff --git a/doc/troubleshoot.html b/doc/troubleshoot.html new file mode 100644 index 0000000..163e8a4 --- /dev/null +++ b/doc/troubleshoot.html @@ -0,0 +1,43 @@ + +troubleshooting phpLogCon + +

    troubleshooting phpLogCon

    +

    Having trouble with phpLogCon? +This page provides some tips on where to look for help and what to do +if you need to ask for assistance. This page is continously being expanded. +

    Useful troublehshooting ressources are: +

      +
    • The phpLogCon documentation - note that the online version always covers +the most recent development version. However, there is a version-specific +doc set in each tarball. If you installed phpLogCon from a package, there usually +is a phpLogCon-doc package, that often needs to be installed separately. If running the default +installation, you should simply be able to get to the correct documentation version +by clicking on the "help" button on any phpLogCon page. +
    • The phpLogCon wiki +(part of the larger rsyslog wiki) provides user tips and experiences. +
    • Check the bugzilla to see if your problem is a known +(and even fixed ;)) bug. +
    +

    Configuration Problems +

    Validate your text configuration settings and, if you use the user system, also the +configuration stored in your database. Some very few configuration settings can only +be applied in the text config files. +

    Asking for Help +

    If you can't find the answer yourself, you should look at these places for +community help. +

      +
    • The phpLogCon forum. This is +the preferred method of obtaining support. +
    • The phpLogCon mailing list. +This is a low-volume list which occasional gets traffic spikes. +The mailing list is probably a good place for complex questions. +
    +

    [manual index] +[phpLogCon site]

    +

    This documentation is part of the +phpLogCon project.
    +Copyright © 2008 by Adiscon. +Released under the GNU GPL version 2 or higher.

    + + + From feebd70f15129654919775b62fac1e602b2f0925 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 15:25:42 +0200 Subject: [PATCH 120/142] Fixed manual link again --- src/include/functions_common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index f4914fc..effb495 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -478,7 +478,7 @@ function InitRuntimeInformations() // --- Check and Set manual link if ( is_dir($gl_root_path . "doc") ) - $content['PHPLOGCON_HELPLINK'] = $content['BASEPATH'] . "doc"; + $content['PHPLOGCON_HELPLINK'] = $content['BASEPATH'] . "doc/manual.html"; else $content['PHPLOGCON_HELPLINK'] = "http://www.phplogcon.org/doc"; // --- From 3851e732566196808a392e20a5196337d28c3901 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 16:46:16 +0200 Subject: [PATCH 121/142] Added new Option to limit display length of string type fields. This really helps making the message list more readable. Popup Details will still contain the string fields in full length. --- src/include/config.sample.php | 3 ++- src/include/constants_filters.php | 1 + src/index.php | 28 ++++++++++++++++++++++------ src/lang/de/main.php | 2 +- src/lang/en/main.php | 2 +- src/lang/pt_BR/main.php | 2 +- 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 8c1a14b..6c3596b 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -65,7 +65,8 @@ $CFG['DebugUserLogin'] = 0; // if enabled, you will see additional informati // --- Default Frontend Options $CFG['PrependTitle'] = ""; // If set, this text will be prepended withint the title tag $CFG['ViewUseTodayYesterday'] = 1; // If enabled, the date from today and yesterday is displayed as "today" and "yesterday" -$CFG['ViewMessageCharacterLimit'] = 80; // Default character limit for the message gets trunscated. +$CFG['ViewMessageCharacterLimit'] = 80; // Default character limit for the message gets trunscated! 0 means NO trunscation. +$CFG['ViewStringCharacterLimit'] = 30; // Default character limit for all other string type fields before they get trunscated! 0 means NO trunscation. $CFG['ViewEntriesPerPage'] = 50; // Default number of syslog entries shown per page $CFG['ViewEnableDetailPopups'] = 1; // If enabled, you will see additional Details for each syslog message on mouse over. $CFG['ViewDefaultTheme'] = "default"; // This sets the default theme the user is going to see when he opens phplogcon the first time. diff --git a/src/include/constants_filters.php b/src/include/constants_filters.php index 3b74cd8..459951d 100644 --- a/src/include/constants_filters.php +++ b/src/include/constants_filters.php @@ -111,5 +111,6 @@ $content['filter_severity_list'][] = array( "ID" => SYSLOG_DEBUG, "DisplayName" $content['filter_messagetype_list'][] = array( "ID" => IUT_Syslog, "DisplayName" => "Syslog", "selected" => "" ); $content['filter_messagetype_list'][] = array( "ID" => IUT_NT_EventReport, "DisplayName" => "WinEventLog", "selected" => "" ); $content['filter_messagetype_list'][] = array( "ID" => IUT_File_Monitor, "DisplayName" => "File Monitor", "selected" => "" ); +$content['filter_messagetype_list'][] = array( "ID" => IUT_APACHELOG, "DisplayName" => "Apache Webserver Logfile", "selected" => "" ); ?> \ No newline at end of file diff --git a/src/index.php b/src/index.php index 73c9d1b..e68c88d 100644 --- a/src/index.php +++ b/src/index.php @@ -290,6 +290,11 @@ if ( isset($content['Sources'][$currentSourceID]) ) //echo $content['main_currentpagenumber']; // --- + // --- Obtain characters limits first! + $myMsgCharLimit = GetConfigSetting("ViewMessageCharacterLimit", 80, CFGLEVEL_USER); + $myStrCharLimit = GetConfigSetting("ViewStringCharacterLimit", 30, CFGLEVEL_USER); + // --- + //Loop through the messages! do { @@ -551,8 +556,9 @@ if ( isset($content['Sources'][$currentSourceID]) ) } else if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_STRING ) { - // kindly copy! + // Kindly Copy Value first! $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = $logArray[$mycolkey]; + $content['syslogmessages'][$counter]['values'][$mycolkey]['rawfieldvalue'] = $logArray[$mycolkey]; // helper variable used for Popups! // Convert into filter format for submenus $szFilterEncodedStr = str_replace(' ', '+', $logArray[$mycolkey]); @@ -566,10 +572,10 @@ if ( isset($content['Sources'][$currentSourceID]) ) // Set truncasted message for display if ( isset($logArray[SYSLOG_MESSAGE]) ) { - // Copy tmp - $tmpCharLimit = GetConfigSetting("ViewMessageCharacterLimit", 80, CFGLEVEL_USER); - - $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[SYSLOG_MESSAGE]) > $tmpCharLimit ? substr($logArray[SYSLOG_MESSAGE], 0, $tmpCharLimit) . " ..." : $logArray[SYSLOG_MESSAGE]); + if ( $myMsgCharLimit > 0 ) + $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[SYSLOG_MESSAGE]) > $myMsgCharLimit ? substr($logArray[SYSLOG_MESSAGE], 0, $myMsgCharLimit) . " ..." : $logArray[SYSLOG_MESSAGE]); + else + $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes($logArray[SYSLOG_MESSAGE]); // Enable LINK property! for this field $content['syslogmessages'][$counter]['values'][$mycolkey]['ismessagefield'] = true; @@ -625,7 +631,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // --- } else // Just set field value - $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] = $myfield['fieldvalue']; + $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] = isset($myfield['rawfieldvalue']) ? $myfield['rawfieldvalue'] : $myfield['fieldvalue']; } } @@ -767,6 +773,14 @@ if ( isset($content['Sources'][$currentSourceID]) ) 'IconSource' => $content['MENU_BULLET_BLUE'] ); } + + // --- Check for reached string character limit + if ( $mycolkey != SYSLOG_MESSAGE ) + { + if ( $myStrCharLimit > 0 ) + $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[$mycolkey]) > $myStrCharLimit ? substr($logArray[$mycolkey], 0, $myStrCharLimit) . " ..." : $logArray[$mycolkey]); + } + // --- } } } @@ -919,6 +933,8 @@ function PrepareStringForSearch($myString) { return str_replace(" ", "+", $myString); } + + // --- ?> \ No newline at end of file diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 7efb6eb..80bcb00 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -171,7 +171,7 @@ $content['LN_FIELDS_MESSAGE'] = "Meldung"; $content['LN_FIELDS_EVENTSOURCE'] = "Event Source"; $content['LN_FIELDS_EVENTCATEGORY'] = "Event Category"; $content['LN_FIELDS_EVENTUSER'] = "Event User"; - $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; + $content['LN_FIELDS_WEBLOG_USER'] = "HTTP User"; $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; $content['LN_FIELDS_WEBLOG_URL'] = "URL"; $content['LN_FIELDS_WEBLOG_QUERYSTRING'] = "Querystring"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 3481253..080df65 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -171,7 +171,7 @@ $content['LN_FIELDS_EVENTLOGTYPE'] = "Eventlog Type"; $content['LN_FIELDS_EVENTSOURCE'] = "Event Source"; $content['LN_FIELDS_EVENTCATEGORY'] = "Event Category"; $content['LN_FIELDS_EVENTUSER'] = "Event User"; -$content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; +$content['LN_FIELDS_WEBLOG_USER'] = "HTTP User"; $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; $content['LN_FIELDS_WEBLOG_URL'] = "URL"; $content['LN_FIELDS_WEBLOG_QUERYSTRING'] = "Querystring"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 6a584c2..3b60ab0 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -175,7 +175,7 @@ $content['LN_FIELDS_EVENTLOGTYPE'] = "Tipo do Evento"; $content['LN_FIELDS_EVENTSOURCE'] = "Origem do Evento"; $content['LN_FIELDS_EVENTCATEGORY'] = "Categoria do Evento"; $content['LN_FIELDS_EVENTUSER'] = "Evento de Usu´rio"; - $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; + $content['LN_FIELDS_WEBLOG_USER'] = "HTTP User"; $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; $content['LN_FIELDS_WEBLOG_URL'] = "URL"; $content['LN_FIELDS_WEBLOG_QUERYSTRING'] = "Querystring"; From 6de2f6328d3dbb1731bdcd2952ff2e8ba86c2aa2 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 17:25:20 +0200 Subject: [PATCH 122/142] Implemented search function for almost all weblog based fields --- src/classes/logstream.class.php | 80 +++++++++++++++++++++ src/css/menu.css | 2 +- src/index.php | 123 ++++++++++++++++++++++++++------ 3 files changed, 182 insertions(+), 23 deletions(-) diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index c2e7b55..b233ad0 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -507,6 +507,86 @@ abstract class LogStream { $tmpFilterType = FILTER_TYPE_DATE; $tmpTimeMode = DATEMODE_LASTX; break; + /* BEGIN WebLog based fields */ + case SYSLOG_WEBLOG_USER: + $tmpKeyName = SYSLOG_WEBLOG_USER; + $tmpFilterType = FILTER_TYPE_STRING; + break; + case SYSLOG_WEBLOG_METHOD: + $tmpKeyName = SYSLOG_WEBLOG_METHOD; + $tmpFilterType = FILTER_TYPE_STRING; + break; + case SYSLOG_WEBLOG_URL: + $tmpKeyName = SYSLOG_WEBLOG_URL; + $tmpFilterType = FILTER_TYPE_STRING; + break; + + case SYSLOG_WEBLOG_QUERYSTRING: + $tmpKeyName = SYSLOG_WEBLOG_QUERYSTRING; + $tmpFilterType = FILTER_TYPE_STRING; + break; + case SYSLOG_WEBLOG_PVER: + $tmpKeyName = SYSLOG_WEBLOG_PVER; + $tmpFilterType = FILTER_TYPE_STRING; + break; + case SYSLOG_WEBLOG_STATUS: + $tmpKeyName = SYSLOG_WEBLOG_STATUS; + $tmpFilterType = FILTER_TYPE_NUMBER; + // --- Extra numeric Check + if ( isset($tmpValues) ) + { + foreach( $tmpValues as $mykey => $szValue ) + { + if ( is_numeric($szValue[FILTER_TMP_VALUE]) ) + $tmpValues[$mykey][FILTER_TMP_VALUE] = $szValue[FILTER_TMP_VALUE]; + else + $tmpValues[$mykey][FILTER_TMP_VALUE] = ""; + } + } + else + { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) + $tmpArray[FILTER_TMP_VALUE] = ""; + } + // --- + break; + + case SYSLOG_WEBLOG_BYTESSEND: + $tmpKeyName = SYSLOG_WEBLOG_BYTESSEND; + $tmpFilterType = FILTER_TYPE_NUMBER; + // --- Extra numeric Check + if ( isset($tmpValues) ) + { + foreach( $tmpValues as $mykey => $szValue ) + { + if ( is_numeric($szValue[FILTER_TMP_VALUE]) ) + $tmpValues[$mykey][FILTER_TMP_VALUE] = $szValue[FILTER_TMP_VALUE]; + else + $tmpValues[$mykey][FILTER_TMP_VALUE] = ""; + } + } + else + { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) + $tmpArray[FILTER_TMP_VALUE] = ""; + } + // --- + break; + case SYSLOG_WEBLOG_REFERER: + $tmpKeyName = SYSLOG_WEBLOG_REFERER; + $tmpFilterType = FILTER_TYPE_STRING; + break; + case SYSLOG_WEBLOG_USERAGENT: + $tmpKeyName = SYSLOG_WEBLOG_USERAGENT; + $tmpFilterType = FILTER_TYPE_STRING; + break; + /* END WebLog based fields */ default: $tmpFilterType = FILTER_TYPE_UNKNOWN; break; diff --git a/src/css/menu.css b/src/css/menu.css index a9156b3..ca3c4e7 100644 --- a/src/css/menu.css +++ b/src/css/menu.css @@ -50,7 +50,7 @@ position: absolute; top: 12px; left: 4px; /* to position them to the right of their containing block */ - width: 350; /* width is based on the containing block */ + width: auto; /* width is based on the containing block */ } div#menu ul ul, diff --git a/src/index.php b/src/index.php index e68c88d..211fe63 100644 --- a/src/index.php +++ b/src/index.php @@ -552,16 +552,27 @@ if ( isset($content['Sources'][$currentSourceID]) ) 'IconSource' => $content['MENU_BULLET_BLUE'] ); } - + // WebServer Type fields + else if ( $mycolkey == SYSLOG_WEBLOG_STATUS ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_NUMBER, SYSLOG_WEBLOG_STATUS, 'LN_FIELDS_WEBLOG_STATUS', false); + } } else if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_STRING ) { - // Kindly Copy Value first! - $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = $logArray[$mycolkey]; - $content['syslogmessages'][$counter]['values'][$mycolkey]['rawfieldvalue'] = $logArray[$mycolkey]; // helper variable used for Popups! + // Set some basic variables first + $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = $logArray[$mycolkey]; // May contain the field value trunscated + $content['syslogmessages'][$counter]['values'][$mycolkey]['rawfieldvalue'] = $logArray[$mycolkey]; // helper variable used for Popups! + $content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue'] = str_replace(' ', '+', $logArray[$mycolkey]); // Convert into filter format for submenus - // Convert into filter format for submenus - $szFilterEncodedStr = str_replace(' ', '+', $logArray[$mycolkey]); + // --- Check for reached string character limit + if ( $mycolkey != SYSLOG_MESSAGE ) + { + if ( $myStrCharLimit > 0 ) + $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[$mycolkey]) > $myStrCharLimit ? substr($logArray[$mycolkey], 0, $myStrCharLimit) . " ..." : $logArray[$mycolkey]); + } + // --- // Special Handling for the Syslog Message! if ( $mycolkey == SYSLOG_MESSAGE ) @@ -656,7 +667,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) if ( strlen($content['searchstr']) > 0 ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+syslogtag%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+syslogtag%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_GREEN'] ); @@ -664,7 +675,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // More Menu entries $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=syslogtag%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=syslogtag%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_BLUE'] ); @@ -683,7 +694,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) if ( strlen($content['searchstr']) > 0 ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+source%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+source%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_GREEN'] ); @@ -691,7 +702,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // More Menu entries $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=source%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=source%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_BLUE'] ); @@ -706,7 +717,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) if ( strlen($content['searchstr']) > 0 ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventlogtype%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventlogtype%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_GREEN'] ); @@ -714,7 +725,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // More Menu entries $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=eventlogtype%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=eventlogtype%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_BLUE'] ); @@ -733,7 +744,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) if ( strlen($content['searchstr']) > 0 ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventlogsource%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventlogsource%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_GREEN'] ); @@ -741,7 +752,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // More Menu entries $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=eventlogsource%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=eventlogsource%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_BLUE'] ); @@ -760,7 +771,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) if ( strlen($content['searchstr']) > 0 ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventuser%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventuser%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_GREEN'] ); @@ -768,19 +779,48 @@ if ( isset($content['Sources'][$currentSourceID]) ) // More Menu entries $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=eventuser%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=eventuser%3A%3D' . urlencode($content['syslogmessages'][$counter]['values'][$mycolkey]['encodedfieldvalue']) . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_BLUE'] ); } - - // --- Check for reached string character limit - if ( $mycolkey != SYSLOG_MESSAGE ) + // WebServer Type fields + else if ( $mycolkey == SYSLOG_WEBLOG_USER ) { - if ( $myStrCharLimit > 0 ) - $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[$mycolkey]) > $myStrCharLimit ? substr($logArray[$mycolkey], 0, $myStrCharLimit) . " ..." : $logArray[$mycolkey]); + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_USER, 'LN_FIELDS_WEBLOG_USER', false); } - // --- + else if ( $mycolkey == SYSLOG_WEBLOG_METHOD ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_METHOD, 'LN_FIELDS_WEBLOG_USERAGENT', false); + } + else if ( $mycolkey == SYSLOG_WEBLOG_URL ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_URL, 'LN_FIELDS_WEBLOG_URL', false); + } + else if ( $mycolkey == SYSLOG_WEBLOG_QUERYSTRING ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_QUERYSTRING, 'LN_FIELDS_WEBLOG_QUERYSTRING', false); + } + else if ( $mycolkey == SYSLOG_WEBLOG_PVER ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_PVER, 'LN_FIELDS_WEBLOG_PVER', false); + } + else if ( $mycolkey == SYSLOG_WEBLOG_REFERER ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_REFERER, 'LN_FIELDS_WEBLOG_REFERER', true); + } + else if ( $mycolkey == SYSLOG_WEBLOG_USERAGENT ) + { + // Add context menu + AddOnClickMenu( $content['syslogmessages'][$counter]['values'][$mycolkey], FILTER_TYPE_STRING, SYSLOG_WEBLOG_USERAGENT, 'LN_FIELDS_WEBLOG_USERAGENT', true); + } + } } } @@ -934,7 +974,46 @@ function PrepareStringForSearch($myString) return str_replace(" ", "+", $myString); } +function AddOnClickMenu(&$fieldGridItem, $fieldType, $szSearchFieldName, $szFieldDisplayNameID, $searchOnline = false) +{ + global $content; + // Set OnClick Menu for SYSLOG_SYSLOGTAG + $fieldGridItem['hasbuttons'] = true; + + // Set FieldSearch Value + if ( $fieldType == FILTER_TYPE_STRING) + $szEncodedFieldValue = urlencode($fieldGridItem['encodedfieldvalue']); + else + $szEncodedFieldValue = $fieldGridItem['fieldvalue']; + + // Menu Option to append filter + if ( strlen($content['searchstr']) > 0 ) + { + $fieldGridItem['buttons'][] = array( + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+' . $szSearchFieldName . '%3A%3D' . $szEncodedFieldValue . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $fieldGridItem['fieldvalue']), + 'IconSource' => $content['MENU_BULLET_GREEN'] + ); + } + + // More Menu entries + $fieldGridItem['buttons'][] = array( + 'ButtonUrl' => '?filter=' . $szSearchFieldName . '%3A%3D' . $szEncodedFieldValue . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $fieldGridItem['fieldvalue']), + 'IconSource' => $content['MENU_BULLET_BLUE'] + ); + + // Add Online Search Button + if ( $searchOnline ) + { + $fieldGridItem['buttons'][] = array( + 'ButtonUrl' => 'http://kb.monitorware.com/kbsearch.php?sa=Search&origin=phplogcon&oid=' . $szSearchFieldName . '&q=' . $szEncodedFieldValue, + 'DisplayName' => $content['LN_VIEW_SEARCHFOR'] . " " . $content[$szFieldDisplayNameID] . " '" . $fieldGridItem['fieldvalue'] . "'", + 'IconSource' => $content['MENU_NETWORK'] + ); + } +} // --- ?> \ No newline at end of file From 8b71df037c8a894bc22a3a2bb0b915c5befcc60b Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 17:52:41 +0200 Subject: [PATCH 123/142] Increased default Javascript Menu Timeout from 1,5 to 3 seconds. We should proberly make this a configureable option --- src/css/menu.css | 2 +- src/js/common.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/css/menu.css b/src/css/menu.css index ca3c4e7..c174ac1 100644 --- a/src/css/menu.css +++ b/src/css/menu.css @@ -50,7 +50,7 @@ position: absolute; top: 12px; left: 4px; /* to position them to the right of their containing block */ - width: auto; /* width is based on the containing block */ + width: 400; /* width is based on the containing block */ } div#menu ul ul, diff --git a/src/js/common.js b/src/js/common.js index e0dfdf6..46f1c89 100644 --- a/src/js/common.js +++ b/src/js/common.js @@ -161,7 +161,7 @@ function toggleFormareaVisibility(FormFieldName, FirstHiddenArea, SecondHiddenAr // helper array to keep track of the timeouts! var runningTimeouts = new Array(); -var defaultMenuTimeout = 1500; +var defaultMenuTimeout = 3000; /* * Toggle display type from NONE to BLOCK */ @@ -235,7 +235,6 @@ function DebugShowElementsById(ObjName) } } - /* * Detail popup handling functions */ From 8eff09613ee783a1ef5581be44e777c4a72e16d5 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 17:57:59 +0200 Subject: [PATCH 124/142] Added new ViewStringCharacterLimit option into admin center --- src/admin/index.php | 3 +++ src/include/functions_common.php | 2 ++ src/lang/de/admin.php | 1 + src/lang/en/admin.php | 1 + src/lang/pt_BR/admin.php | 1 + src/templates/admin/admin_index.html | 8 ++++++++ 6 files changed, 16 insertions(+) diff --git a/src/admin/index.php b/src/admin/index.php index 1b4b326..0caf8c8 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -139,6 +139,7 @@ if ( isset($_POST['op']) ) // Read Text number fields if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_POST['ViewMessageCharacterLimit']; } + if ( isset ($_POST['ViewStringCharacterLimit']) && is_numeric($_POST['ViewStringCharacterLimit']) ) { $content['ViewStringCharacterLimit'] = $_POST['ViewStringCharacterLimit']; } if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = $_POST['ViewEntriesPerPage']; } if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = $_POST['ViewEnableAutoReloadSeconds']; } @@ -184,6 +185,7 @@ if ( isset($_POST['op']) ) // Read Text number fields if ( isset ($_POST['User_ViewMessageCharacterLimit']) && is_numeric($_POST['User_ViewMessageCharacterLimit']) ) { $USERCFG['ViewMessageCharacterLimit'] = $_POST['User_ViewMessageCharacterLimit']; } + if ( isset ($_POST['User_ViewStringCharacterLimit']) && is_numeric($_POST['User_ViewStringCharacterLimit']) ) { $USERCFG['ViewStringCharacterLimit'] = $_POST['User_ViewStringCharacterLimit']; } if ( isset ($_POST['User_ViewEntriesPerPage']) && is_numeric($_POST['User_ViewEntriesPerPage']) ) { $USERCFG['ViewEntriesPerPage'] = $_POST['User_ViewEntriesPerPage']; } if ( isset ($_POST['User_ViewEnableAutoReloadSeconds']) && is_numeric($_POST['User_ViewEnableAutoReloadSeconds']) ) { $USERCFG['ViewEnableAutoReloadSeconds'] = $_POST['User_ViewEnableAutoReloadSeconds']; } @@ -291,6 +293,7 @@ if ( $content['ENABLEUSEROPTIONS'] ) // --- Set TextFields! $content['User_PrependTitle'] = GetConfigSetting('PrependTitle', $content['PrependTitle'], CFGLEVEL_USER); $content['User_ViewMessageCharacterLimit'] = GetConfigSetting('ViewMessageCharacterLimit', $content['ViewMessageCharacterLimit'], CFGLEVEL_USER); + $content['User_ViewStringCharacterLimit'] = GetConfigSetting('ViewStringCharacterLimit', $content['ViewStringCharacterLimit'], CFGLEVEL_USER); $content['User_ViewEntriesPerPage'] = GetConfigSetting('ViewEntriesPerPage', $content['ViewEntriesPerPage'], CFGLEVEL_USER); $content['User_ViewEnableAutoReloadSeconds'] = GetConfigSetting('ViewEnableAutoReloadSeconds', $content['ViewEnableAutoReloadSeconds'], CFGLEVEL_USER); $content['User_SearchCustomButtonCaption'] = GetConfigSetting('SearchCustomButtonCaption', $content['SearchCustomButtonCaption'], CFGLEVEL_USER); diff --git a/src/include/functions_common.php b/src/include/functions_common.php index effb495..fe4cfa2 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1191,6 +1191,7 @@ function SaveGeneralSettingsIntoDB() WriteConfigValue( "TreatNotFoundFiltersAsTrue", true ); WriteConfigValue( "ViewMessageCharacterLimit", true ); + WriteConfigValue( "ViewStringCharacterLimit", true ); WriteConfigValue( "ViewEntriesPerPage", true ); WriteConfigValue( "ViewEnableAutoReloadSeconds", true ); @@ -1225,6 +1226,7 @@ function SaveUserGeneralSettingsIntoDB() WriteConfigValue( "TreatNotFoundFiltersAsTrue", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewMessageCharacterLimit", false, $content['SESSION_USERID'] ); + WriteConfigValue( "ViewStringCharacterLimit", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewEntriesPerPage", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewEnableAutoReloadSeconds", false, $content['SESSION_USERID'] ); diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index 0f2f780..7263d11 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -66,6 +66,7 @@ $content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; $content['LN_GEN_USETODAY'] = "Use Today and Yesterday in timefields"; $content['LN_GEN_DETAILPOPUPS'] = "Use Popup to display the full messagedetails"; $content['LN_GEN_MSGCHARLIMIT'] = "Character limit of the message in main view"; +$content['LN_GEN_STRCHARLIMIT'] = "Character display limit for all string type fields"; $content['LN_GEN_ENTRIESPERPAGE'] = "Number of entries per page"; $content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; $content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 2d57f84..437e751 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -65,6 +65,7 @@ $content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; $content['LN_GEN_USETODAY'] = "Use Today and Yesterday in timefields"; $content['LN_GEN_DETAILPOPUPS'] = "Use Popup to display the full messagedetails"; $content['LN_GEN_MSGCHARLIMIT'] = "Character limit of the message in main view"; +$content['LN_GEN_STRCHARLIMIT'] = "Character display limit for all string type fields"; $content['LN_GEN_ENTRIESPERPAGE'] = "Number of entries per page"; $content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; $content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index f26ee40..1b27f1c 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -72,6 +72,7 @@ $content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; $content['LN_GEN_USETODAY'] = "Use Today and Yesterday in timefields"; $content['LN_GEN_DETAILPOPUPS'] = "Use Popup to display the full messagedetails"; $content['LN_GEN_MSGCHARLIMIT'] = "Character limit of the message in main view"; +$content['LN_GEN_STRCHARLIMIT'] = "Character display limit for all string type fields"; $content['LN_GEN_ENTRIESPERPAGE'] = "Number of entries per page"; $content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; $content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 6a1a414..6ff3c18 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -123,6 +123,14 @@
+ + + + + + + + From 5544b0e286bad3bb7e5eac40b33dc49fcd928332 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 12:22:03 +0200 Subject: [PATCH 125/142] Added MsgParsers for Apache and IIS logfiles Also added some more icons and fixed a few things related to the web logs --- src/classes/logstreamconfig.class.php | 8 +- src/classes/logstreamdisk.class.php | 8 +- src/classes/logstreamlineparsermisc.class.php | 77 +++++++++ .../msgparsers/msgparser.apache2.class.php | 146 ++++++++++++++++++ .../msgparsers/msgparser.iis.class.php | 142 +++++++++++++++++ src/images/icons/earth_find.png | Bin 0 -> 1032 bytes src/images/icons/find.png | Bin 0 -> 751 bytes src/images/icons/find_next.png | Bin 0 -> 863 bytes src/images/icons/help2.png | Bin 0 -> 901 bytes src/images/icons/help3.png | Bin 0 -> 899 bytes src/include/constants_errors.php | 2 +- src/include/functions_common.php | 18 +++ src/lang/de/main.php | 2 + src/lang/en/main.php | 1 + src/lang/pt_BR/main.php | 1 + 15 files changed, 399 insertions(+), 6 deletions(-) create mode 100644 src/classes/logstreamlineparsermisc.class.php create mode 100644 src/classes/msgparsers/msgparser.apache2.class.php create mode 100644 src/classes/msgparsers/msgparser.iis.class.php create mode 100644 src/images/icons/earth_find.png create mode 100644 src/images/icons/find.png create mode 100644 src/images/icons/find_next.png create mode 100644 src/images/icons/help2.png create mode 100644 src/images/icons/help3.png diff --git a/src/classes/logstreamconfig.class.php b/src/classes/logstreamconfig.class.php index b473ee4..dafc055 100644 --- a/src/classes/logstreamconfig.class.php +++ b/src/classes/logstreamconfig.class.php @@ -136,9 +136,11 @@ abstract class LogStreamConfig { { foreach( $this->_msgParserObjList as $myMsgParser ) { - // Perform Parsing, and return if was successfull! Otherwise the next Parser will be called. - if ( $myMsgParser->ParseMsg($szMsg, $arrArguments) == SUCCESS ) - return SUCCESS; + // Perform Parsing, and return if was successfull or the message needs to be skipped! + // Otherwise the next Parser will be called. + $ret = $myMsgParser->ParseMsg($szMsg, $arrArguments); + if ( $ret == SUCCESS || $ret == ERROR_MSG_SKIPMESSAGE ) + return $ret; } } diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index b4af26f..3638b0b 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -237,10 +237,14 @@ class LogStreamDisk extends LogStream { if ( $ret == SUCCESS && $bParseMessage) { // Line Parser Hook here - $this->_logStreamConfigObj->_lineParser->ParseLine($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + $retParser = $this->_logStreamConfigObj->_lineParser->ParseLine($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); // Run optional Message Parsers now - $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + $retParser = $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + + // Check if we have to skip the message! + if ( $retParser == ERROR_MSG_SKIPMESSAGE ) + $ret = $retParser; // Set uID to the PropertiesOut! $arrProperitesOut[SYSLOG_UID] = $uID; diff --git a/src/classes/logstreamlineparsermisc.class.php b/src/classes/logstreamlineparsermisc.class.php new file mode 100644 index 0000000..d067314 --- /dev/null +++ b/src/classes/logstreamlineparsermisc.class.php @@ -0,0 +1,77 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * Dummy LogStream Parser Class for all other logfile types handeled + * by msg parsers + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- Basic Includes +require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'include/constants_errors.php'); +require_once($gl_root_path . 'include/constants_logstream.php'); +// --- + + +class LogStreamLineParsermisc extends LogStreamLineParser { +// protected $_arrProperties = null; + + // Constructor + public function LogStreamLineParsermisc() { + return; // Nothing + } + + /** + * ParseLine + * + * @param arrArguments array in&out: properties of interest. There can be no guarantee the logstream can actually deliver them. + * @return integer Error stat + */ + public function ParseLine($szLine, &$arrArguments) + { + // Set MSG Property + $arrArguments[SYSLOG_MESSAGE] = $szLine; + + // Set IUT Property + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_Unknown; + + // Return success! + return SUCCESS; + } + + +} + +?> \ No newline at end of file diff --git a/src/classes/msgparsers/msgparser.apache2.class.php b/src/classes/msgparsers/msgparser.apache2.class.php new file mode 100644 index 0000000..11cc3b7 --- /dev/null +++ b/src/classes/msgparsers/msgparser.apache2.class.php @@ -0,0 +1,146 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * Apache Logfile Parser used to split WebLog fields if found + * in the msg + * * + * All directives are explained within this file * + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- Basic Includes +require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'classes/msgparser.class.php'); +require_once($gl_root_path . 'include/constants_errors.php'); +require_once($gl_root_path . 'include/constants_logstream.php'); +// --- + +class MsgParser_apache2 extends MsgParser { +// protected $_arrProperties = null; + + // Constructor + public function MsgParser_apache2() { + return; // Nothing + } + + /** + * ParseLine + * + * @param arrArguments array in&out: properties of interest. There can be no guarantee the logstream can actually deliver them. + * @return integer Error stat + */ + public function ParseMsg($szMsg, &$arrArguments) + { + global $content, $fields; + +//return ERROR_MSG_NOMATCH; + + // LogFormat "%h %l %u %t \"%r\" %>s %b" common + // LogFormat "%{Referer}i -> %U" referer + // LogFormat "%{User-agent}i" agent + // LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + + // Sample (apache2): 127.0.0.1 - - [14/Sep/2008:06:50:15 +0200] "GET / HTTP/1.0" 200 19023 "-" "VoilaBot link checker" + // Sample: 65.55.211.112 - - [16/Sep/2008:13:37:47 +0200] "GET /index.php?name=News&file=article&sid=1&theme=Printer HTTP/1.1" 200 4908 "-" "msnbot/1.1 (+http://search.msn.com/msnbot.htm)" + if ( preg_match('/(.|.*?) (.|.*?) (.|.*?) \[(.*?)\] "(.*?) (.*?) (.*?)" (.|[0-9]{1,12}) (.|[0-9]{1,12}) "(.|.*?)" "(.*?)("|)$/', $szMsg, $out ) ) + { +// print_r ( $out ); +// exit; + + // Set generic properties + $arrArguments[SYSLOG_HOST] = $out[1]; + $arrArguments[SYSLOG_DATE] = GetEventTime($out[4]); + + // Set weblog specific properties! + $arrArguments[SYSLOG_WEBLOG_USER] = $out[3]; + $arrArguments[SYSLOG_WEBLOG_METHOD] = $out[5]; + if ( strpos($out[6], "?") === false ) + { + $arrArguments[SYSLOG_WEBLOG_URL] = $out[6]; + $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= ""; + } + else + { + $arrArguments[SYSLOG_WEBLOG_URL] = substr( $out[6], 0, strpos($out[6], "?")); + $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= substr( $out[6], strpos($out[6], "?")+1 ); + } + +// $arrArguments[SYSLOG_WEBLOG_QUERYSTRING] = $out[7]; + $arrArguments[SYSLOG_WEBLOG_PVER] = $out[7]; + $arrArguments[SYSLOG_WEBLOG_STATUS] = $out[8]; + $arrArguments[SYSLOG_WEBLOG_BYTESSEND] = $out[9]; + $arrArguments[SYSLOG_WEBLOG_REFERER] = $out[10]; + $arrArguments[SYSLOG_WEBLOG_USERAGENT] = $out[11]; + + // Set msg to whole logline + $arrArguments[SYSLOG_MESSAGE] = $out[0]; + + if ( $this->_MsgNormalize == 1 ) + { + //Init tmp msg + $szTmpMsg = ""; + + // Create Field Array to prepend into msg! Reverse Order here + $myFields = array( SYSLOG_WEBLOG_USER, SYSLOG_WEBLOG_PVER, SYSLOG_WEBLOG_USERAGENT, SYSLOG_WEBLOG_BYTESSEND, SYSLOG_WEBLOG_STATUS, SYSLOG_WEBLOG_REFERER, SYSLOG_WEBLOG_METHOD, SYSLOG_WEBLOG_QUERYSTRING, SYSLOG_WEBLOG_URL ); + + foreach ( $myFields as $myField ) + { + // Set Field Caption + if ( isset($content[ $fields[$myField]['FieldCaptionID'] ]) ) + $szFieldName = $content[ $fields[$myField]['FieldCaptionID'] ]; + else + $szFieldName = $fields[$myField]['FieldCaptionID']; + + // Append Field into msg + $szTmpMsg = $szFieldName . ": '" . $arrArguments[$myField] . "'\n" . $szTmpMsg; + } + + // copy finished MSG back! + $arrArguments[SYSLOG_MESSAGE] = $szTmpMsg; + } + } + else + { + // return no match in this case! + return ERROR_MSG_NOMATCH; + } + + // Set IUT Property if success! + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_APACHELOG; + + // If we reached this position, return success! + return SUCCESS; + } +} + +?> \ No newline at end of file diff --git a/src/classes/msgparsers/msgparser.iis.class.php b/src/classes/msgparsers/msgparser.iis.class.php new file mode 100644 index 0000000..bdf98a1 --- /dev/null +++ b/src/classes/msgparsers/msgparser.iis.class.php @@ -0,0 +1,142 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * Microsoft IIS Logfile Parser used to split WebLog fields if found + * in the msg + * * + * All directives are explained within this file * + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- Basic Includes +require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'classes/msgparser.class.php'); +require_once($gl_root_path . 'include/constants_errors.php'); +require_once($gl_root_path . 'include/constants_logstream.php'); +// --- + +class MsgParser_iis extends MsgParser { +// protected $_arrProperties = null; + + // Constructor + public function MsgParser_iis() { + return; // Nothing + } + + /** + * ParseLine + * + * @param arrArguments array in&out: properties of interest. There can be no guarantee the logstream can actually deliver them. + * @return integer Error stat + */ + public function ParseMsg($szMsg, &$arrArguments) + { + global $content, $fields; + + // Special case here, if loglines start with #, they are comments and have to be skipped! + $iSharpPos = strpos($szMsg, "#"); + if ( $iSharpPos !== false && $iSharpPos == 0 ) + return ERROR_MSG_SKIPMESSAGE; + + // LogFormat: date time cs-method cs-uri-stem cs-uri-query cs-username c-ip cs-version cs(User-Agent) cs(Referer) sc-status sc-bytes + // Sample: 2008-09-17 00:15:24 GET /Include/MyStyleV2.css - - 208.111.154.249 HTTP/1.0 Mozilla/5.0+(X11;+U;+Linux+i686+(x86_64);+en-US;+rv:1.8.1.11)+Gecko/20080109+(Charlotte/0.9t;+http://www.searchme.com/support/) http://www.adiscon.com/Common/en/News/MWCon-2005-09-12.php 200 1812 + if ( preg_match('/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}) (.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?)$/', $szMsg, $out ) ) + { +// print_r ( $out ); +// exit; + + // Set generic properties + $arrArguments[SYSLOG_DATE] = GetEventTime($out[1]); + $arrArguments[SYSLOG_HOST] = $out[6]; + + // Set weblog specific properties! + $arrArguments[SYSLOG_WEBLOG_METHOD] = $out[2]; +// $arrArguments[SYSLOG_WEBLOG_USER] = $out[3]; + if ( strpos($out[3], "?") === false ) + { + $arrArguments[SYSLOG_WEBLOG_URL] = $out[3]; + $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= ""; + } + else + { + $arrArguments[SYSLOG_WEBLOG_URL] = substr( $out[6], 0, strpos($out[3], "?")); + $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= substr( $out[6], strpos($out[3], "?")+1 ); + } + $arrArguments[SYSLOG_WEBLOG_PVER] = $out[7]; + $arrArguments[SYSLOG_WEBLOG_USERAGENT] = $out[8]; + $arrArguments[SYSLOG_WEBLOG_REFERER] = $out[9]; + $arrArguments[SYSLOG_WEBLOG_STATUS] = $out[10]; + $arrArguments[SYSLOG_WEBLOG_BYTESSEND] = $out[11]; + + // Set msg to whole logline + $arrArguments[SYSLOG_MESSAGE] = $out[0]; + + if ( $this->_MsgNormalize == 1 ) + { + //Init tmp msg + $szTmpMsg = ""; + + // Create Field Array to prepend into msg! Reverse Order here + $myFields = array( SYSLOG_WEBLOG_USER, SYSLOG_WEBLOG_PVER, SYSLOG_WEBLOG_USERAGENT, SYSLOG_WEBLOG_BYTESSEND, SYSLOG_WEBLOG_STATUS, SYSLOG_WEBLOG_REFERER, SYSLOG_WEBLOG_METHOD, SYSLOG_WEBLOG_QUERYSTRING, SYSLOG_WEBLOG_URL ); + + foreach ( $myFields as $myField ) + { + // Set Field Caption + if ( isset($content[ $fields[$myField]['FieldCaptionID'] ]) ) + $szFieldName = $content[ $fields[$myField]['FieldCaptionID'] ]; + else + $szFieldName = $fields[$myField]['FieldCaptionID']; + + // Append Field into msg + $szTmpMsg = $szFieldName . ": '" . $arrArguments[$myField] . "'\n" . $szTmpMsg; + } + + // copy finished MSG back! + $arrArguments[SYSLOG_MESSAGE] = $szTmpMsg; + } + } + else + { + // return no match in this case! + return ERROR_MSG_NOMATCH; + } + + // Set IUT Property if success! + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_APACHELOG; + + // If we reached this position, return success! + return SUCCESS; + } +} + +?> \ No newline at end of file diff --git a/src/images/icons/earth_find.png b/src/images/icons/earth_find.png new file mode 100644 index 0000000000000000000000000000000000000000..d1639e65287b09b145fc92352a76b9531c8fe439 GIT binary patch literal 1032 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 ZEFej4V=l=ZK6U^A002ovPDHLkV1iH|rELHJ literal 0 HcmV?d00001 diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index c80bfe4..7d26187 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -62,6 +62,6 @@ define('ERROR_DB_DBFIELDNOTFOUND', 19); define('ERROR_MSG_NOMATCH', 18); define('ERROR_CHARTS_NOTCONFIGURED', 20); - +define('ERROR_MSG_SKIPMESSAGE', 21); ?> diff --git a/src/include/functions_common.php b/src/include/functions_common.php index fe4cfa2..537adb8 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -519,8 +519,13 @@ function InitFrontEndVariables() $content['MENU_SELECTION_DISABLED'] = $content['BASEPATH'] . "images/icons/selection.png"; $content['MENU_SELECTION_ENABLED'] = $content['BASEPATH'] . "images/icons/selection_delete.png"; $content['MENU_TEXT_FIND'] = $content['BASEPATH'] . "images/icons/text_find.png"; + $content['MENU_EARTH_FIND'] = $content['BASEPATH'] . "images/icons/earth_find.png"; + $content['MENU_FIND'] = $content['BASEPATH'] . "images/icons/find.png"; + $content['MENU_NEXT_FIND'] = $content['BASEPATH'] . "images/icons/find_next.png"; $content['MENU_NETWORK'] = $content['BASEPATH'] . "images/icons/earth_network.png"; $content['MENU_HELP'] = $content['BASEPATH'] . "images/icons/help.png"; + $content['MENU_HELP_BLUE'] = $content['BASEPATH'] . "images/icons/help2.png"; + $content['MENU_HELP_ORANGE'] = $content['BASEPATH'] . "images/icons/help3.png"; $content['MENU_KB'] = $content['BASEPATH'] . "images/icons/books.png"; $content['MENU_DOCUMENTVIEW'] = $content['BASEPATH'] . "images/icons/document_view.png"; $content['MENU_DATAEDIT'] = $content['BASEPATH'] . "images/icons/data_edit.png"; @@ -1051,6 +1056,8 @@ function AddContextLinks(&$sourceTxt) */ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) { + global $content; + // Create string $szReturn = $prepend; if ( strlen($szIP) > 0 ) @@ -1070,9 +1077,20 @@ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) else // Normal LINK! $szReturn .= '' . $szIP . ''; + + // Add InfoSearch Link + $szReturn .= ''; } else if ( strlen($szDomain) > 0 ) + { $szReturn .= '' . $szDomain . ''; + + // Add InfoSearch Link + $szReturn .= ''; + + } + + // Append the append string now $szReturn .= $append; // return result diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 80bcb00..6ea1ee3 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -87,6 +87,8 @@ $content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden. $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; + $content['LN_GEN_MOREINFORMATION'] = "More Information"; + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 080df65..7c363f9 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -88,6 +88,7 @@ $content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least o $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; + $content['LN_GEN_MOREINFORMATION'] = "More Information"; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 3b60ab0..ef87c45 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -91,6 +91,7 @@ $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; + $content['LN_GEN_MOREINFORMATION'] = "More Information"; // Topmenu Entries From 3e2ea6c19a1d98ff584fcdd59293b070168fe8e6 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 13:25:07 +0200 Subject: [PATCH 126/142] Fixed filtering issue when string fields were empty --- src/classes/logstreamdisk.class.php | 7 ++++ .../msgparsers/msgparser.iis.class.php | 41 ++++++++++++------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 3638b0b..d9d778f 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -770,6 +770,13 @@ class LogStreamDisk extends LogStream { break; } } + else + { + // Either filter value or property value was empty! + // This means we have no match + $bEval = false; + } + break; case FILTER_TYPE_NUMBER: $bIsOrFilter = true; // Default is set to TRUE diff --git a/src/classes/msgparsers/msgparser.iis.class.php b/src/classes/msgparsers/msgparser.iis.class.php index bdf98a1..d15eb33 100644 --- a/src/classes/msgparsers/msgparser.iis.class.php +++ b/src/classes/msgparsers/msgparser.iis.class.php @@ -63,11 +63,32 @@ class MsgParser_iis extends MsgParser { { global $content, $fields; +// $iSharpPos = strpos($szMsg, "#"); +// if ( $iSharpPos !== false && $iSharpPos == 0 ) +// return ERROR_MSG_SKIPMESSAGE; + // Special case here, if loglines start with #, they are comments and have to be skipped! - $iSharpPos = strpos($szMsg, "#"); - if ( $iSharpPos !== false && $iSharpPos == 0 ) - return ERROR_MSG_SKIPMESSAGE; + if ( ($iSharpPos = strpos($szMsg, "#")) !== false && $iSharpPos == 0 ) + { + // Only init fields then + // Set generic properties + $arrArguments[SYSLOG_DATE] = ""; + $arrArguments[SYSLOG_HOST] = ""; + // Set weblog specific properties! + $arrArguments[SYSLOG_WEBLOG_METHOD] = ""; + $arrArguments[SYSLOG_WEBLOG_URL] = ""; + $arrArguments[SYSLOG_WEBLOG_QUERYSTRING] = ""; + $arrArguments[SYSLOG_WEBLOG_USER] = ""; + $arrArguments[SYSLOG_WEBLOG_PVER] = ""; + $arrArguments[SYSLOG_WEBLOG_USERAGENT] = ""; + $arrArguments[SYSLOG_WEBLOG_REFERER] = ""; + $arrArguments[SYSLOG_WEBLOG_STATUS] = ""; + $arrArguments[SYSLOG_WEBLOG_BYTESSEND] = ""; + + // Set msg to whole logline + $arrArguments[SYSLOG_MESSAGE] = $szMsg; + } // LogFormat: date time cs-method cs-uri-stem cs-uri-query cs-username c-ip cs-version cs(User-Agent) cs(Referer) sc-status sc-bytes // Sample: 2008-09-17 00:15:24 GET /Include/MyStyleV2.css - - 208.111.154.249 HTTP/1.0 Mozilla/5.0+(X11;+U;+Linux+i686+(x86_64);+en-US;+rv:1.8.1.11)+Gecko/20080109+(Charlotte/0.9t;+http://www.searchme.com/support/) http://www.adiscon.com/Common/en/News/MWCon-2005-09-12.php 200 1812 if ( preg_match('/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}) (.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?) (.|.*?)$/', $szMsg, $out ) ) @@ -81,17 +102,9 @@ class MsgParser_iis extends MsgParser { // Set weblog specific properties! $arrArguments[SYSLOG_WEBLOG_METHOD] = $out[2]; -// $arrArguments[SYSLOG_WEBLOG_USER] = $out[3]; - if ( strpos($out[3], "?") === false ) - { - $arrArguments[SYSLOG_WEBLOG_URL] = $out[3]; - $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= ""; - } - else - { - $arrArguments[SYSLOG_WEBLOG_URL] = substr( $out[6], 0, strpos($out[3], "?")); - $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= substr( $out[6], strpos($out[3], "?")+1 ); - } + $arrArguments[SYSLOG_WEBLOG_URL] = $out[3]; + $arrArguments[SYSLOG_WEBLOG_QUERYSTRING]= $out[4]; + $arrArguments[SYSLOG_WEBLOG_USER] = $out[5]; $arrArguments[SYSLOG_WEBLOG_PVER] = $out[7]; $arrArguments[SYSLOG_WEBLOG_USERAGENT] = $out[8]; $arrArguments[SYSLOG_WEBLOG_REFERER] = $out[9]; From 5d32582188b3c255627702501b3ec8970a11c62c Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 14:04:48 +0200 Subject: [PATCH 127/142] Added two new general options, once to control the php script timeout and one to control the popupmenu timeout --- src/admin/index.php | 5 +++++ src/include/config.sample.php | 3 +++ src/include/functions_common.php | 20 +++++++++++++++---- src/js/common.js | 2 +- src/lang/de/admin.php | 2 ++ src/lang/en/admin.php | 2 ++ src/lang/en/main.php | 7 +++++++ src/lang/pt_BR/admin.php | 17 +++++++++------- src/templates/admin/admin_index.html | 29 ++++++++++++++++++++-------- src/templates/include_footer.html | 19 ++++++++++-------- src/templates/include_header.html | 3 +++ 11 files changed, 81 insertions(+), 28 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index 0caf8c8..fbbdea9 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -140,8 +140,10 @@ if ( isset($_POST['op']) ) // Read Text number fields if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_POST['ViewMessageCharacterLimit']; } if ( isset ($_POST['ViewStringCharacterLimit']) && is_numeric($_POST['ViewStringCharacterLimit']) ) { $content['ViewStringCharacterLimit'] = $_POST['ViewStringCharacterLimit']; } + if ( isset ($_POST['PopupMenuTimeout']) && is_numeric($_POST['PopupMenuTimeout']) ) { $content['PopupMenuTimeout'] = $_POST['PopupMenuTimeout']; } if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = $_POST['ViewEntriesPerPage']; } if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = $_POST['ViewEnableAutoReloadSeconds']; } + if ( isset ($_POST['MiscMaxExecutionTime']) && is_numeric($_POST['MiscMaxExecutionTime']) ) { $content['MiscMaxExecutionTime'] = $_POST['MiscMaxExecutionTime']; } // Read Text fields if ( isset ($_POST['PrependTitle']) ) { $content['PrependTitle'] = $_POST['PrependTitle']; } @@ -186,8 +188,10 @@ if ( isset($_POST['op']) ) // Read Text number fields if ( isset ($_POST['User_ViewMessageCharacterLimit']) && is_numeric($_POST['User_ViewMessageCharacterLimit']) ) { $USERCFG['ViewMessageCharacterLimit'] = $_POST['User_ViewMessageCharacterLimit']; } if ( isset ($_POST['User_ViewStringCharacterLimit']) && is_numeric($_POST['User_ViewStringCharacterLimit']) ) { $USERCFG['ViewStringCharacterLimit'] = $_POST['User_ViewStringCharacterLimit']; } + if ( isset ($_POST['User_PopupMenuTimeout']) && is_numeric($_POST['User_PopupMenuTimeout']) ) { $USERCFG['PopupMenuTimeout'] = $_POST['User_PopupMenuTimeout']; } if ( isset ($_POST['User_ViewEntriesPerPage']) && is_numeric($_POST['User_ViewEntriesPerPage']) ) { $USERCFG['ViewEntriesPerPage'] = $_POST['User_ViewEntriesPerPage']; } if ( isset ($_POST['User_ViewEnableAutoReloadSeconds']) && is_numeric($_POST['User_ViewEnableAutoReloadSeconds']) ) { $USERCFG['ViewEnableAutoReloadSeconds'] = $_POST['User_ViewEnableAutoReloadSeconds']; } +// TODO!!!!!!!!!!!111111111 // Read Text fields if ( isset ($_POST['User_PrependTitle']) ) { $USERCFG['PrependTitle'] = $_POST['User_PrependTitle']; } @@ -294,6 +298,7 @@ if ( $content['ENABLEUSEROPTIONS'] ) $content['User_PrependTitle'] = GetConfigSetting('PrependTitle', $content['PrependTitle'], CFGLEVEL_USER); $content['User_ViewMessageCharacterLimit'] = GetConfigSetting('ViewMessageCharacterLimit', $content['ViewMessageCharacterLimit'], CFGLEVEL_USER); $content['User_ViewStringCharacterLimit'] = GetConfigSetting('ViewStringCharacterLimit', $content['ViewStringCharacterLimit'], CFGLEVEL_USER); + $content['User_PopupMenuTimeout'] = GetConfigSetting('PopupMenuTimeout', $content['PopupMenuTimeout'], CFGLEVEL_USER); $content['User_ViewEntriesPerPage'] = GetConfigSetting('ViewEntriesPerPage', $content['ViewEntriesPerPage'], CFGLEVEL_USER); $content['User_ViewEnableAutoReloadSeconds'] = GetConfigSetting('ViewEnableAutoReloadSeconds', $content['ViewEnableAutoReloadSeconds'], CFGLEVEL_USER); $content['User_SearchCustomButtonCaption'] = GetConfigSetting('SearchCustomButtonCaption', $content['SearchCustomButtonCaption'], CFGLEVEL_USER); diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 6c3596b..a453468 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -59,6 +59,8 @@ $CFG['MiscShowDebugGridCounter'] = 0; // Only for debugging purposes, will add $CFG["MiscShowPageRenderStats"] = 1; // If enabled, you will see Pagerender Settings $CFG['MiscEnableGzipCompression'] = 1; // If enabled, phplogcon will use gzip compression for output, we recommend // to have this option enabled, it will highly reduce bandwith usage. +$CFG['MiscMaxExecutionTime'] = 30; // phpLogCon will try to overwrite the default script timeout with this value during runtime! + // This can of course only work if phpLogCon is allowed to changed the script timeout. $CFG['DebugUserLogin'] = 0; // if enabled, you will see additional informations on failed logins // --- @@ -80,6 +82,7 @@ $CFG['SearchCustomButtonSearch'] = "error"; // Default search string for the $CFG['EnableIPAddressResolve'] = 1; // If enabled, IP Addresses inline messages are automatically resolved and the result is added in brackets {} behind the IP Address $CFG['SuppressDuplicatedMessages'] = 0; // If enabled, duplicated messages will be suppressed in the main display. $CFG['TreatNotFoundFiltersAsTrue'] = 0; // If you filter / search for messages, and the fields you are filtering for is not found, the filter result is treaten as TRUE! +$CFG['PopupMenuTimeout'] = 3000; // This variable defines the default timeout value for popup menus in milliseconds. (those menus which popup when you click on the value of a field. // --- // --- Define which fields you want to see diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 537adb8..539eb10 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -434,13 +434,13 @@ function InitPhpDebugMode() function CheckAndSetRunMode() { - global $content, $RUNMODE, $MaxExecutionTime; + global $content, $RUNMODE; // Set to command line mode if argv is set! if ( !isset($_SERVER["GATEWAY_INTERFACE"]) ) $RUNMODE = RUNMODE_COMMANDLINE; // Obtain max_execution_time - $MaxExecutionTime = ini_get("max_execution_time"); + $content['MaxExecutionTime'] = ini_get("max_execution_time"); // Define and Inits Syslog variables now! define_syslog_variables(); @@ -482,6 +482,15 @@ function InitRuntimeInformations() else $content['PHPLOGCON_HELPLINK'] = "http://www.phplogcon.org/doc"; // --- + + // --- Try to extend the script timeout if possible! + $iTmp = GetConfigSetting("MiscMaxExecutionTime", 30, CFGLEVEL_GLOBAL); + if ( $iTmp != $content['MaxExecutionTime'] && $iTmp > 10 ) + { //Try to extend the runtime in this case! + @ini_set("max_execution_time", $iTmp); + $content['MaxExecutionTime'] = ini_get("max_execution_time"); + } + // --- } function CreateDebugModes() @@ -1102,11 +1111,11 @@ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) */ function ReverseResolveIP( $szIP, $prepend, $append ) { - global $gl_starttime, $MaxExecutionTime; + global $gl_starttime, $content; // Substract 5 savety seconds! $scriptruntime = intval(microtime_float() - $gl_starttime); - if ( $scriptruntime > ($MaxExecutionTime-5) ) + if ( $scriptruntime > ($content['MaxExecutionTime']-5) ) return ""; // Abort if these IP's are postet @@ -1212,6 +1221,7 @@ function SaveGeneralSettingsIntoDB() WriteConfigValue( "ViewStringCharacterLimit", true ); WriteConfigValue( "ViewEntriesPerPage", true ); WriteConfigValue( "ViewEnableAutoReloadSeconds", true ); + WriteConfigValue( "PopupMenuTimeout", true ); WriteConfigValue( "PrependTitle", true ); WriteConfigValue( "SearchCustomButtonCaption", true ); @@ -1224,6 +1234,7 @@ function SaveGeneralSettingsIntoDB() // GLOBAL ONLY WriteConfigValue( "DebugUserLogin", true ); WriteConfigValue( "MiscDebugToSyslog", true ); + WriteConfigValue( "MiscMaxExecutionTime", true ); } function SaveUserGeneralSettingsIntoDB() @@ -1247,6 +1258,7 @@ function SaveUserGeneralSettingsIntoDB() WriteConfigValue( "ViewStringCharacterLimit", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewEntriesPerPage", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewEnableAutoReloadSeconds", false, $content['SESSION_USERID'] ); + WriteConfigValue( "PopupMenuTimeout", false, $content['SESSION_USERID'] ); WriteConfigValue( "PrependTitle", false, $content['SESSION_USERID'] ); WriteConfigValue( "SearchCustomButtonCaption", false, $content['SESSION_USERID'] ); diff --git a/src/js/common.js b/src/js/common.js index 46f1c89..56e79ff 100644 --- a/src/js/common.js +++ b/src/js/common.js @@ -161,7 +161,7 @@ function toggleFormareaVisibility(FormFieldName, FirstHiddenArea, SecondHiddenAr // helper array to keep track of the timeouts! var runningTimeouts = new Array(); -var defaultMenuTimeout = 3000; +// MOVED INTO HEADER var defaultMenuTimeout = 3000; /* * Toggle display type from NONE to BLOCK */ diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index 7263d11..f548d4b 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -87,6 +87,8 @@ $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options" $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; $content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; $content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; +$content['LN_GEN_POPUPMENUTIMEOUT'] = "Popupmenu Timeout in milli seconds"; +$content['LN_ADMIN_SCRIPTTIMEOUT'] = "PHP Script Timeout in seconds"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 437e751..2235ada 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -88,6 +88,8 @@ $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options" $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; $content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; $content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; +$content['LN_GEN_POPUPMENUTIMEOUT'] = "Popupmenu Timeout in milli seconds"; +$content['LN_ADMIN_SCRIPTTIMEOUT'] = "PHP Script Timeout in seconds"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 7c363f9..7de84fb 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -90,6 +90,13 @@ $content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least o $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; $content['LN_GEN_MOREINFORMATION'] = "More Information"; + $content['LN_FOOTER_PAGERENDERED'] = "Page rendered in"; + $content['LN_FOOTER_DBQUERIES'] = "DB queries"; + $content['LN_FOOTER_GZIPENABLED'] = "GZIP enabled"; + $content['LN_FOOTER_SCRIPTTIMEOUT'] = "Script Timeout"; + $content['LN_FOOTER_SECONDS'] = "seconds"; + + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; $content['LN_MENU_SHOWEVENTS'] = "Show Events"; diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index 1b27f1c..f548d4b 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -49,13 +49,7 @@ $content['LN_GEN_GLOBAL'] = "Global"; $content['LN_GEN_USERONLY_LONG'] = "For me only
(Only available to your user)"; $content['LN_GEN_GROUPONLY_LONG'] = "For this group
(Only available to the selected group)"; $content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; -$content['LN_GEN_OPTIONNAME'] = "Option name"; -$content['LN_GEN_GLOBALVALUE'] = "Global value"; -$content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; -$content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; -$content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; -$content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; -$content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; + // General Options $content['LN_ADMIN_GLOBFRONTEND'] = "Global frontend options"; @@ -86,6 +80,15 @@ $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; $content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; +$content['LN_GEN_OPTIONNAME'] = "Option name"; +$content['LN_GEN_GLOBALVALUE'] = "Global value"; +$content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; +$content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; +$content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; +$content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; +$content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; +$content['LN_GEN_POPUPMENUTIMEOUT'] = "Popupmenu Timeout in milli seconds"; +$content['LN_ADMIN_SCRIPTTIMEOUT'] = "PHP Script Timeout in seconds"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 6ff3c18..830490e 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -118,33 +118,41 @@
- + - + - + - + - + - + - + - + + + + + + + + + @@ -230,6 +238,11 @@ + + + + + diff --git a/src/templates/include_footer.html b/src/templates/include_footer.html index 8e46353..e1d9da3 100644 --- a/src/templates/include_footer.html +++ b/src/templates/include_footer.html @@ -1,22 +1,25 @@
{LN_GROUP_USERDELETE}: '{GROUPNAME}'
{LN_USER_NAME} diff --git a/src/templates/admin/admin_searches.html b/src/templates/admin/admin_searches.html new file mode 100644 index 0000000..0dd8a72 --- /dev/null +++ b/src/templates/admin/admin_searches.html @@ -0,0 +1,93 @@ + + + +
+

{ERROR_MSG}

+
+ + + + + + + + + +
{LN_SEARCH_CENTER}
+

+ + + + + + + + + + + + + + + + + + + + + + + + +
{LN_SEARCH_ID}{LN_SEARCH_NAME}{LN_SEARCH_QUERY}{LN_SEARCH_TYPE}{LN_GEN_ACTIONS}
{ID}{DisplayName}{SearchQuery_Display} {SearchTypeText} +   +   +
 {LN_SEARCH_ADD}
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_SEARCH_ADDEDIT}
{LN_SEARCH_NAME}
{LN_SEARCH_QUERY}
{LN_SEARCH_USERONLY}
{LN_SEARCH_GROUPONLY} + +
+ + + +
+
+ + +

+ +
+ + \ No newline at end of file diff --git a/src/templates/admin/admin_users.html b/src/templates/admin/admin_users.html index f4f9173..3ed324e 100644 --- a/src/templates/admin/admin_users.html +++ b/src/templates/admin/admin_users.html @@ -44,6 +44,7 @@ + From 037356b55490de5a6b4dddf5a8b94dd7c4b7e22b Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 18 Jul 2008 16:17:47 +0200 Subject: [PATCH 016/142] Start adding views admin into admin center. But not fully functional yet, working on it. --- src/admin/searches.php | 32 +- src/admin/views.php | 457 ++++++++++++++++++++++++ src/images/icons/delete_disabled.png | Bin 0 -> 893 bytes src/images/icons/edit_disabled.png | Bin 0 -> 894 bytes src/images/icons/gear.png | Bin 0 -> 995 bytes src/include/functions_common.php | 5 +- src/include/functions_config.php | 58 ++- src/include/functions_users.php | 33 ++ src/lang/en/admin.php | 35 +- src/templates/admin/admin_searches.html | 4 +- src/templates/admin/admin_views.html | 128 +++++++ 11 files changed, 736 insertions(+), 16 deletions(-) create mode 100644 src/admin/views.php create mode 100644 src/images/icons/delete_disabled.png create mode 100644 src/images/icons/edit_disabled.png create mode 100644 src/images/icons/gear.png create mode 100644 src/templates/admin/admin_views.html diff --git a/src/admin/searches.php b/src/admin/searches.php index 3b4ce41..2d77f40 100644 --- a/src/admin/searches.php +++ b/src/admin/searches.php @@ -73,8 +73,15 @@ if ( isset($_GET['op']) ) $content['userid'] = null; $content['CHECKED_ISUSERONLY'] = ""; $content['SEARCHID'] = ""; - + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + $content['ISGROUPSAVAILABLE'] = true; + else + $content['ISGROUPSAVAILABLE'] = false; + + /* $sqlquery = "SELECT " . DB_GROUPS . ".ID as mygroupid, " . DB_GROUPS . ".groupname " . @@ -93,7 +100,7 @@ if ( isset($_GET['op']) ) array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); } else - $content['ISGROUPSAVAILABLE'] = false; + $content['ISGROUPSAVAILABLE'] = false;*/ // --- } else if ($_GET['op'] == "edit") @@ -124,6 +131,26 @@ if ( isset($_GET['op']) ) else $content['CHECKED_ISUSERONLY'] = ""; + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- +/* // --- Check if groups are available $sqlquery = "SELECT " . DB_GROUPS . ".ID as mygroupid, " . @@ -150,6 +177,7 @@ if ( isset($_GET['op']) ) else $content['ISGROUPSAVAILABLE'] = false; // --- +*/ } else { diff --git a/src/admin/views.php b/src/admin/views.php new file mode 100644 index 0000000..738ac95 --- /dev/null +++ b/src/admin/views.php @@ -0,0 +1,457 @@ + Helps administrating custom user views + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +// Only if the user is an admin! +//if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) +// DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWVIEW'] = "true"; + $content['VIEW_FORMACTION'] = "addnewview"; + $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_ADD']; + + //PreInit these values + $content['DisplayName'] = ""; + $content['userid'] = null; + $content['CHECKED_ISUSERONLY'] = ""; + $content['VIEWID'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + $content['ISGROUPSAVAILABLE'] = true; + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWVIEW'] = "true"; + $content['VIEW_FORMACTION'] = "editview"; + $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_EDIT']; + + // View must be loaded as well already! + if ( isset($_GET['id']) && $content['VIEWS'][$_GET['id']] ) + { + //PreInit these values + $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT ID, DisplayName " . + " FROM " . DB_VIEWS . + " WHERE ID = " . $content['VIEWID']; + + $result = DB_Query($sqlquery); + $myview = DB_GetSingleRow($result, true); + if ( isset($myview['DisplayName']) ) + { + $content['VIEWID'] = $myview['ID']; + +/* + $content['DisplayName'] = $mysearch['DisplayName']; + $content['SearchQuery'] = $mysearch['SearchQuery']; + if ( $mysearch['userid'] != null ) + $content['CHECKED_ISUSERONLY'] = "checked"; + else + $content['CHECKED_ISUSERONLY'] = ""; +*/ + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $myview['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $myview['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else + { + $content['ISEDITORNEWVIEW'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); + } + } + else + { + $content['ISEDITORNEWVIEW'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_INVALIDID']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); + + // Get UserInfo + $result = DB_Query("SELECT DisplayName FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['DisplayName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); + } + + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_VIEWS_WARNDELETEVIEW'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_DELSEARCH'], $content['VIEWID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "views.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_INVALIDID']; + } + } +} + +// --- Additional work todo for the edit view +if ( isset($content['ISEDITORNEWVIEW']) && $content['ISEDITORNEWVIEW'] ) +{ + // Read Columns from FORM data! + if ( isset($_POST['Columns']) ) + { + // --- Read Columns from Formdata + if ( is_array($_POST['Columns']) ) + { + } + else // One element only + $content['COLUMNS'][$_POST['Columns']]['ColFieldID'] = $_POST['Columns']; + // --- + + // --- Process Columns for display + $i = 0; // Help counter! + foreach ($content['COLUMNS'] as $key => &$myColumn ) + { + // Set Fieldcaption + if ( isset($content[ $fields[$key]['FieldCaptionID'] ]) ) + $myColumn['ColCaption'] = $content[ $fields[$key]['FieldCaptionID'] ]; + else + $myColumn['ColCaption'] = $key; + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $myColumn['colcssclass'] = "line1"; + else + $myColumn['colcssclass'] = "line2"; + $i++; + // --- + } + // --- + } + + // --- Copy fields data array + $content['FIELDS'] = $fields; + + // removed already added fields and set fieldcaption + foreach ($content['FIELDS'] as $key => &$myField ) + { + // Set Fieldcaption + if ( isset($content[ $myField['FieldCaptionID'] ]) ) + $myField['FieldCaption'] = $content[ $myField['FieldCaptionID'] ]; + else + $myField['FieldCaption'] = $key; + } + // --- + +} +// --- + +// --- Process POST Form Data +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['VIEWID'] = DB_RemoveBadChars($_POST['id']); } else {$content['VIEWID'] = ""; } + if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } +// if ( isset ($_POST['SearchQuery']) ) { $content['SearchQuery'] = DB_RemoveBadChars($_POST['SearchQuery']); } else {$content['SearchQuery'] = ""; } + + // User & Group handeled specially + if ( isset ($_POST['isuseronly']) ) + { + $content['userid'] = $content['SESSION_USERID']; + $content['groupid'] = "null"; // Either user or group not both! + } + else + { + $content['userid'] = "null"; + if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) + $content['groupid'] = intval($_POST['groupid']); + else + $content['groupid'] = "null"; + } + + // --- Check mandotary values + if ( $content['DisplayName'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY']; + } + // --- + + if ( !isset($content['ISERROR']) ) + { + // Check subop's first! + if ( isset($_POST['subop']) ) + { + // Get NewColID + $szNewColID = DB_RemoveBadChars($_POST['newcolumn']); + + // Add a new Column into our list! + if ( $_POST['subop'] == $content['LN_VIEWS_ADDCOLUMN'] && isset($_POST['newcolumn']) ) + { + // Add New entry into columnlist + $content['COLUMNS'][$szNewColID]['ColFieldID'] = $szNewColID; + // Set Fieldcaption + if ( isset($content[ $fields[$szNewColID]['FieldCaptionID'] ]) ) + $content['COLUMNS'][$szNewColID]['ColCaption'] = $content[ $fields[$szNewColID]['FieldCaptionID'] ]; + else + $content['COLUMNS'][$szNewColID]['ColCaption'] = $szNewColID; + + // Set CSSClass + $content['COLUMNS'][$szNewColID]['colcssclass'] = count($content['COLUMNS']) % 2 == 0 ? "line1" : "line2"; + } +// else if () +// { +// } + } + else // Now SUBOP means normal processing! + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewsearch" ) + { + // Add custom search now! + $sqlquery = "INSERT INTO " . DB_SEARCHES . " (DisplayName, SearchQuery, userid, groupid) + VALUES ('" . $content['DisplayName'] . "', + '" . $content['SearchQuery'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENADDED'], $content['DisplayName'] ) , "searches.php" ); + } + else if ( $_POST['op'] == "editsearch" ) + { + $result = DB_Query("SELECT ID FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + else + { + // Edit the Search Entry now! + $result = DB_Query("UPDATE " . DB_SEARCHES . " SET + DisplayName = '" . $content['DisplayName'] . "', + SearchQuery = '" . $content['SearchQuery'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SEARCHID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENEDIT'], $content['DisplayName']) , "searches.php" ); + } + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Searches + $content['LISTVIEWS'] = "true"; +/* + // Read all Serverentries + $sqlquery = "SELECT " . + DB_VIEWS . ".ID, " . + DB_VIEWS . ".DisplayName, " . + DB_VIEWS . ".Columns, " . + DB_VIEWS . ".userid, " . + DB_VIEWS . ".groupid, " . + DB_USERS . ".username, " . + DB_GROUPS . ".groupname " . + " FROM " . DB_VIEWS . + " LEFT OUTER JOIN (" . DB_USERS . ", " . DB_GROUPS . + ") ON (" . + DB_VIEWS . ".userid=" . DB_USERS . ".ID AND " . + DB_VIEWS . ".groupid=" . DB_GROUPS . ".ID " . + ") " . + " ORDER BY " . DB_VIEWS . ".userid, " . DB_VIEWS . ".groupid, " . DB_VIEWS . ".DisplayName"; +//echo $sqlquery; + $result = DB_Query($sqlquery); + $content['VIEWS'] = DB_GetAllRows($result, true); +*/ + + // Copy Views array for further modifications + $content['VIEWS'] = $content['Views']; + + // --- Process Users + $i = 0; // Help counter! + foreach ($content['VIEWS'] as &$myView ) + { + // So internal Views can not be edited but seen + if ( is_numeric($myView['ID']) ) + { + $myView['ActionsAllowed'] = true; + + // --- Set Image for Type + if ( $myView['userid'] != null ) + { + $myView['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; + $myView['SearchTypeText'] = $content["LN_GEN_USERONLY"]; + } + else if ( $myView['groupid'] != null ) + { + $myView['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; + $myView['SearchTypeText'] = $content["LN_GEN_GROUPONLY"]; + } + else + { + $myView['SearchTypeImage'] = $content["MENU_GLOBAL"]; + $myView['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; + } + // --- + } + else + { + $myView['ActionsAllowed'] = false; + + $myView['SearchTypeImage'] = $content["MENU_INTERNAL"]; + $myView['SearchTypeText'] = $content["LN_GEN_INTERNAL"]; + } + + // --- Add DisplayNames to columns + $iBegin = true; + foreach ($myView['Columns'] as $myCol ) + { + // Get Fieldcaption + if ( isset($content[ $fields[$myCol]['FieldCaptionID'] ]) ) + $myView['COLUMNS'][$myCol]['FieldCaption'] = $content[ $fields[$myCol]['FieldCaptionID'] ]; + else + $myView['COLUMNS'][$myCol]['FieldCaption'] = $myCol; + + if ( $iBegin ) + { + $myView['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ""; + $iBegin = false; + } + else + $myView['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ", "; + + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $myView['cssclass'] = "line1"; + else + $myView['cssclass'] = "line2"; + $i++; + // --- + } + // --- +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_VIEWSOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_views.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/images/icons/delete_disabled.png b/src/images/icons/delete_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d856cdd09dbc8dfa942425b2773bd187d9acffce GIT binary patch literal 893 zcmV-@1A_dCP) z000W>0fLJSS^xk82}wjjRCt_alTAotcNE2s4!X&d%76^Cj0>a74&qKXf*?d5Rz=Df zQ{$pUNb(}akdcJEynOHk37CKod14;H5~W?J0Wljvf-660gOa@7q}W zwu=jw|DW^Y-org0iI);Z@vYnK_PX8fyd+5_zu#Z7+wJ-3>1ls=clS2{S^zYCsd;Sz zfq*FziTt^@x5wk-WA5+ob9;N6nx-)pi_z(H{xLi}{3C#`0lfJaAQ%h|$K&z8PESvn z%jKx5DmOMZxVE;&cs$N%G)hHLD2n29e}BINKs$hDl7QFi{VE&|m(R}5xVgDWRaLpZ zzD`wDnM@|Ryu8e-t1D(Q8HPe3TCLWy!C(;dNt!S>Hz#GYS>|#%CK3srpP#c@t#WB; ziHf4|@$r!)xw^VaS(fQ?x!Bv=y8+-k04?x%Jg515o|>jHl}a&{O0ia}@#5lwH#avV z$%BIf%CbyZmg#o8X)>991MmZYcaS9MA)C!InM_huRffZ1USD7H>FJ4$MuWv-k+Li^ z6bez2B)VKKPE1Tx0eAs?fZy+b%w#f5Bob5;ouM*#0r&*q9RRcl zg7A~wZm++H0)YU1J|7ns7rC&oK%33Rsi`TBjg8gY+uMHz5C-rOKt; zOGOk#&d<+N6h+R?&eCeNa&mH#f*{=M;uQcU0Br!8UI5Ji-Wv>t?|XWBw4tG)-$zGB z??*;P?gs`23Pz*x7k%uoE`I-d%CDgS-ssei`hK5vrxm^br2n=3FHB!RngF!w=5zos z>eZpYwf;BGe*+nJ2TAfD%x3@q02*{fSad;kbZBpK099;dVPqgvd2@7SZF4LjNp52< T$sIm+00000NkvXXu0mjf)uNMS literal 0 HcmV?d00001 diff --git a/src/images/icons/edit_disabled.png b/src/images/icons/edit_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..1fa39aab78023a857cb93fbad6cd1953caec0d21 GIT binary patch literal 894 zcmV-^1A+XBP) z000W>0fLJSS^xk83Q0skRCt_ikwHjX2LOiuv>S9il%efdahuhHqbrr^WtSZUF$>Dm z7>E(ygzgZdf(H}PA|Xks-~%NNTsGhAVTL#8CQd~NvouwL5HRkAo%H53Z-O}A3rkR+ zwC~>~mVqAr+y8xs|3@ekx-W|2N@r*1jK|}7>~_1qb~>GnEiEk%>~{NY0M`N7`uh5w zilX?h!{JC&RaM;wa07r1;c)nQFc^F(2tryAgumL`+xJ^rTQhBKZGX7k?rc+2(_=+Z z0+J+Iv$M1G`Fw0>Xm|zSA%Gj0pP&CHnM}S3hr{gc?WND>qa;a;L?YJ6$Vgrggul18 zw$2lY1Y@xn1AzeTc6$zh7r=*56lHgJclS*$m*d*n8dp|U7>~!PC<+%A7U=i;sVE9} zc6Jzx#pv~VUze4Y{RF@d;3LSg{HLz#Rz9C+Hk+kk7;iHDRF#!Y@ME-QdL!+pPzGeb(Iqn6I@1MmpIy$hHm5DW%q_xARzVzJ0{ zI!#^IIX*tlo}M0dbac?;@vy$WK3*E_QvmN?z$Agu(b4Gk_O@l3CRJ7C;o%{ho0~Z} zILNuVIcl25#>U2<0eAqE{~sm^3=a=K+1%W;bX{jEm7=C;R8^IxX;Rm99vmFd>2xjt z=mb!9N#OVU0~;F~mSGrNUtj0Z(GhdG9Ccmi+1VNM`8-8Y{2jnI06w@%;7ulz7D;9@ z8Sd}zGnGm)9*-A>hK4c@hvT=(%F6EmhydAKGW+`J?J@E(9K01N>L0_X+c0`Mh(&j5S^;C88JyGn2kz%2lkrH}6d zc)tW(2VlDjyvTp`$Q6JC{~H_t000_vMObt}b#!QNasX9qWnp9>Q+acAWo>gTAW3dx UF3BA}b^rhX07*qoM6N<$f(o&XFaQ7m literal 0 HcmV?d00001 diff --git a/src/images/icons/gear.png b/src/images/icons/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1411f2876ffc929705cba5e08946a3f1aba8ab GIT binary patch literal 995 zcmV<9104K`P)WdKueAT%IKVQwHYFfcSAF*!OgI65>iAS*C2FfbHbcz*x@00(qQO+^RR z1s4r65*v&It^fc432;bRa{vGf5dZ)S5dnW>Uy%R+00d`2O+f$vv5yPS~^};SD(l@or9k4?!oIe+u6%3tMTysbR4&c+xL_I1D6%p5gQwu2hN|j=UFVJ zJ)NBoAK7eWciP$9Y&*;E-28dY__U> zCX)_D6Y%oo3yz`~qOq|}$8k=7)`>*O0)2h1SZk{_e{(YlOG`0Gr81z?v0HsKnt*g# z0*}YL_3!ltg#P}!%|74M1ct_B2Si1Mqnu&X^`b}sO;b=%z`*=`5JZsztyYNw87M5& z!Ja)v5QH@S?c3x=DCAwh*s!9jtIrXMtmVbyo4BZkl9GIQ@Zg0sH1vFgVTkypOP!}0 z8Y~)ukWkbI!^0oe{r)N0cnPwgC@NA9x3{0B6$%Q1!8qC!f#bX$m+R>t-0$~mBp9&Nd1v6wZ2AfHtPk|aT;((EEh{Y-Q7)x?n_j&m$a zs0o6=c@;D@9ooh7i4u{F`0~nPEw#$(&$D42@53% zDOgzvW3?VHhe8X}!Qgx#cMs&t6RoemSa;@3BNGa(V;%&|b}?2Uf%!;7Ag~6zcN;;Y zVIds$&$`{OcsV!7RIZHT-n|WJEYAg}bDH}6dCVV+iRzk~GID%;8b(JaB%0QM!B9-h z&G}RuH~Jo9-Xb0(zI7897spnj(X@}_#`#a52E3xkCaS9H%HF*BkQ^Gi^Tg$v`2Oox zs%B>9{YWIj%eU|gB3DTyvPY5oLZk{|YinDMm6z|o$#D~JXJ=n~ac)ehWZ7g=FyQzn zGK#F`0=GXbRrY-_$U&q|E*0WL1Vn?ZBM~IMQ($|OldDDYvw`7UK(X{ R?9%`M002ovPDHLkV1ip}$Qu9v literal 0 HcmV?d00001 diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 648d5f5..39b136c 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -432,6 +432,9 @@ function InitFrontEndVariables() $content['MENU_EDIT'] = $content['BASEPATH'] . "images/icons/edit.png"; $content['MENU_DELETE'] = $content['BASEPATH'] . "images/icons/delete.png"; $content['MENU_GLOBAL'] = $content['BASEPATH'] . "images/icons/earth.png"; + $content['MENU_INTERNAL'] = $content['BASEPATH'] . "images/icons/gear.png"; + $content['MENU_EDIT_DISABLED'] = $content['BASEPATH'] . "images/icons/edit_disabled.png"; + $content['MENU_DELETE_DISABLED'] = $content['BASEPATH'] . "images/icons/delete_disabled.png"; $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; @@ -513,7 +516,7 @@ function InitConfigurationValues() LoadSearchesFromDatabase(); // Load Configured Views -// LoadViewsFromDatabase(); + LoadViewsFromDatabase(); // Load Configured Sources // LoadSourcesFromDatabase(); diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 7a308be..e7b2dda 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -209,11 +209,15 @@ function InitViewConfigs() 'ID' => "SYSLOG", 'DisplayName' =>"Syslog Fields", 'Columns' => array ( SYSLOG_DATE, SYSLOG_FACILITY, SYSLOG_SEVERITY, SYSLOG_HOST, SYSLOG_SYSLOGTAG, SYSLOG_PROCESSID, SYSLOG_MESSAGETYPE, SYSLOG_MESSAGE ), + 'userid' => null, + 'groupid' => null, ); $CFG['Views']['EVTRPT']= array( 'ID' => "EVTRPT", 'DisplayName' =>"EventLog Fields", 'Columns' => array ( SYSLOG_DATE, SYSLOG_HOST, SYSLOG_SEVERITY, SYSLOG_EVENT_LOGTYPE, SYSLOG_EVENT_SOURCE, SYSLOG_EVENT_ID, SYSLOG_EVENT_USER, SYSLOG_MESSAGE ), + 'userid' => null, + 'groupid' => null, ); // Set default of 'DefaultViewsID' @@ -303,7 +307,6 @@ function LoadSearchesFromDatabase() global $CFG, $content; // --- Create SQL Query - // Create Where for USERID if ( isset($content['SESSION_LOGGEDIN']) && $content['SESSION_LOGGEDIN'] ) $szWhereUser = " OR " . DB_SEARCHES . ".userid = " . $content['SESSION_USERID'] . " "; @@ -314,14 +317,15 @@ function LoadSearchesFromDatabase() $szGroupWhere = " OR " . DB_SEARCHES . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ")"; else $szGroupWhere = ""; - $sqlquery = " SELECT * " . " FROM " . DB_SEARCHES . " WHERE (" . DB_SEARCHES . ".userid IS NULL AND " . DB_SEARCHES . ".groupid IS NULL) " . $szWhereUser . $szGroupWhere . " ORDER BY " . DB_SEARCHES . ".userid, " . DB_SEARCHES . ".groupid, " . DB_SEARCHES . ".DisplayName"; -// " ORDER BY " . DB_SEARCHES . ".DisplayName"; + // --- + + // Get Searches from DB now! $result = DB_Query($sqlquery); $myrows = DB_GetAllRows($result, true); if ( isset($myrows ) && count($myrows) > 0 ) @@ -329,11 +333,51 @@ function LoadSearchesFromDatabase() // Overwrite Search Array with Database one $CFG['Search'] = $myrows; $content['Search'] = $myrows; - - // Cleanup searches and fill / load from database - - } } +function LoadViewsFromDatabase() +{ + // Needed to make global + global $CFG, $content; + + // --- Create SQL Query + // Create Where for USERID + if ( isset($content['SESSION_LOGGEDIN']) && $content['SESSION_LOGGEDIN'] ) + $szWhereUser = " OR " . DB_VIEWS . ".userid = " . $content['SESSION_USERID'] . " "; + else + $szWhereUser = ""; + + if ( isset($content['SESSION_GROUPIDS']) ) + $szGroupWhere = " OR " . DB_VIEWS . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ")"; + else + $szGroupWhere = ""; + $sqlquery = " SELECT * " . + " FROM " . DB_VIEWS . + " WHERE (" . DB_VIEWS . ".userid IS NULL AND " . DB_VIEWS . ".groupid IS NULL) " . + $szWhereUser . + $szGroupWhere . + " ORDER BY " . DB_VIEWS . ".userid, " . DB_VIEWS . ".groupid, " . DB_VIEWS . ".DisplayName"; + // --- + + // Get Views from DB now! + $result = DB_Query($sqlquery); + $myrows = DB_GetAllRows($result, true); + if ( isset($myrows ) && count($myrows) > 0 ) + { + // Overwrite existing Views array + unset($CFG['Views']); + print_r ( $CFG['Views'] ); + exit; + + // ReINIT Views Array + InitViewConfigs(); + + // Merge into existing Views Array! + $CFG['Views'] = array_merge ( $CFG['Views'], $myrows ); + $content['Views'] = $CFG['Views']; + } + +} + ?> \ No newline at end of file diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 2dc8a1b..081110e 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -222,4 +222,37 @@ function RedirectToDatabaseUpgrade() // --- END Usermanagement Function --- +/* +* Helper function to obtain a list of groups for display +*/ +function GetGroupsForSelectfield() +{ + global $content; + + $sqlquery = "SELECT " . + DB_GROUPS . ".ID as mygroupid, " . + DB_GROUPS . ".groupname " . + "FROM " . DB_GROUPS . + " ORDER BY " . DB_GROUPS . ".groupname"; + $result = DB_Query($sqlquery); + $mygroups = DB_GetAllRows($result, true); + if ( isset($mygroups) && count($mygroups) > 0 ) + { + // Process All Groups + for($i = 0; $i < count($mygroups); $i++) + $mygroups[$i]['group_selected'] = ""; + + // Enable Group Selection + array_unshift( $mygroups, array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); + + // return result + return $mygroups; + } + else + return false; + // --- +} + + + ?> \ No newline at end of file diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 38f581e..939464e 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -66,6 +66,10 @@ $content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; $content['LN_GEN_CUSTBTNCAPT'] = "Custom search caption"; $content['LN_GEN_CUSTBTNSRCH'] = "Custom search string"; $content['LN_GEN_SUCCESSFULLYSAVED'] = "The configuration Values have been successfully saved"; +$content['LN_GEN_INTERNAL'] = "Internal"; +$content['LN_GEN_DISABLED'] = "Function disabled"; +$content['LN_GEN_USERONLY'] = "For me only
(Only available to your user)"; +$content['LN_GEN_GROUPONLY'] = "For this group
(Only available to the selected group)"; $content['LN_GEN_'] = ""; // User Center @@ -128,16 +132,14 @@ $content['LN_GROUP_'] = ""; // Custom Searches center $content['LN_SEARCH_CENTER'] = "Custom Searches"; -$content['LN_SEARCH_ADD'] = "Add Custom Search"; +$content['LN_SEARCH_ADD'] = "Add new Custom Search"; $content['LN_SEARCH_ID'] = "ID"; $content['LN_SEARCH_NAME'] = "Search Name"; $content['LN_SEARCH_QUERY'] = "Search Query"; -$content['LN_SEARCH_TYPE'] = "Type of Search"; +$content['LN_SEARCH_TYPE'] = "Assigned to"; $content['LN_SEARCH_EDIT'] = "Edit Custom Search"; $content['LN_SEARCH_DELETE'] = "Delete Custom Search"; $content['LN_SEARCH_ADDEDIT'] = "Add / Edit a Custom Search"; -$content['LN_SEARCH_USERONLY'] = "For me only
(Only available to your user)"; -$content['LN_SEARCH_GROUPONLY'] = "For this group
(Only available to the selected group)"; $content['LN_SEARCH_SELGROUPENABLE'] = ">> Select Group to enable <<"; $content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; $content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY'] = "The SearchQuery cannot be empty."; @@ -151,5 +153,30 @@ $content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been succes $content['LN_SEARCH_'] = ""; $content['LN_SEARCH_'] = ""; +// Custom Searches center +$content['LN_VIEWS_CENTER'] = "Views Options"; +$content['LN_VIEWS_ID'] = "ID"; +$content['LN_VIEWS_NAME'] = "View Name"; +$content['LN_VIEWS_COLUMNS'] = "View Columns"; +$content['LN_VIEWS_TYPE'] = "Assigned to"; +$content['LN_VIEWS_ADD'] = "Add new View"; +$content['LN_VIEWS_EDIT'] = "Edit View"; +$content['LN_VIEWS_ERROR_IDNOTFOUND'] = "A View with ID '%1' could not be found."; +$content['LN_VIEWS_ERROR_INVALIDID'] = "The View with ID '%1' is not a valid View."; +$content['LN_VIEWS_WARNDELETEVIEW'] = "Are you sure that you want to delete the View '%1'? This cannot be undone!"; +$content['LN_VIEWS_ERROR_DELSEARCH'] = "Deleting of the View with id '%1' failed!"; +$content['LN_VIEWS_ERROR_HASBEENDEL'] = "The View '%1' has been successfully deleted!"; +$content['LN_VIEWS_ADDEDIT'] = "Add / Edit a View"; +$content['LN_VIEWS_COLUMNLIST'] = "Configured Columns"; +$content['LN_VIEWS_ADDCOLUMN'] = "Add Column into list"; +$content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; +$content['LN_VIEWS_'] = ""; +$content['LN_VIEWS_'] = ""; +$content['LN_VIEWS_'] = ""; +$content['LN_VIEWS_'] = ""; +$content['LN_VIEWS_'] = ""; +$content['LN_VIEWS_'] = ""; +$content['LN_VIEWS_'] = ""; + ?> \ No newline at end of file diff --git a/src/templates/admin/admin_searches.html b/src/templates/admin/admin_searches.html index 0dd8a72..9abae0c 100644 --- a/src/templates/admin/admin_searches.html +++ b/src/templates/admin/admin_searches.html @@ -54,11 +54,11 @@
- + - + diff --git a/src/templates/admin/admin_views.html b/src/templates/admin/admin_views.html new file mode 100644 index 0000000..7d60431 --- /dev/null +++ b/src/templates/admin/admin_views.html @@ -0,0 +1,128 @@ + + + +
+

{ERROR_MSG}

+
+ + +
{LN_USER_ADDEDIT}
{LN_USER_NAME}
{LN_SEARCH_QUERY}{LN_GEN_USERONLY}
{LN_SEARCH_USERONLY}{LN_GEN_USERONLY}
+ + + + + + +
{LN_VIEWS_CENTER}
+

+ + + + + + + + + + + + + + + + + + + + + + +
{LN_VIEWS_ID}{LN_VIEWS_NAME}{LN_VIEWS_COLUMNS}{LN_VIEWS_TYPE}{LN_GEN_ACTIONS}
{ID}{DisplayName} + {FieldCaptionSeperator}{FieldCaption} + {SearchTypeText} + +   +   + + +   +   + +
 {LN_VIEWS_ADD}
+ + + +
+ + + + + + + + + + + + + + + + + + +
{LN_VIEWS_ADDEDIT}
{LN_VIEWS_NAME}
{LN_GEN_USERONLY}
{LN_GEN_GROUPONLY} + +
+
+ + + + + + + + + + + + + +
{LN_VIEWS_COLUMNLIST}
+ + {ColFieldID} {ColCaption} + +   +
+ + + +
+
+ + + + +
+
+ + + +

+
+
+ + +

+ +
+ + \ No newline at end of file From 64a92aabf9d4b9c7f15dcf99de36a83f4a913959 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 18 Jul 2008 17:36:19 +0200 Subject: [PATCH 017/142] Views Admin almost completely working. Need to code moving up and down of columns when adding / editing a new View. --- src/admin/views.php | 74 ++++++++++++++++++++++----- src/css/defaults.css | 8 +++ src/images/icons/nav_down_blue.png | Bin 0 -> 904 bytes src/images/icons/nav_up_blue.png | Bin 0 -> 913 bytes src/include/functions_common.php | 2 + src/lang/en/admin.php | 4 +- src/templates/admin/admin_views.html | 26 ++++++---- 7 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 src/images/icons/nav_down_blue.png create mode 100644 src/images/icons/nav_up_blue.png diff --git a/src/admin/views.php b/src/admin/views.php index 738ac95..231c17f 100644 --- a/src/admin/views.php +++ b/src/admin/views.php @@ -201,14 +201,17 @@ if ( isset($content['ISEDITORNEWVIEW']) && $content['ISEDITORNEWVIEW'] ) // --- Read Columns from Formdata if ( is_array($_POST['Columns']) ) { + // Copy columns ID's + foreach ($_POST['Columns'] as $myColKey) + $content['SUBCOLUMNS'][$myColKey]['ColFieldID'] = $myColKey; } else // One element only - $content['COLUMNS'][$_POST['Columns']]['ColFieldID'] = $_POST['Columns']; + $content['SUBCOLUMNS'][$_POST['Columns']]['ColFieldID'] = $_POST['Columns']; // --- // --- Process Columns for display $i = 0; // Help counter! - foreach ($content['COLUMNS'] as $key => &$myColumn ) + foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) { // Set Fieldcaption if ( isset($content[ $fields[$key]['FieldCaptionID'] ]) ) @@ -225,12 +228,21 @@ if ( isset($content['ISEDITORNEWVIEW']) && $content['ISEDITORNEWVIEW'] ) // --- } // --- + +// print_r ( $content['COLUMNS'] ); } // --- Copy fields data array $content['FIELDS'] = $fields; + + // removed already added fields + foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) + { + if ( isset($content['FIELDS'][$key]) ) + unset($content['FIELDS'][$key]); + } - // removed already added fields and set fieldcaption + // set fieldcaption foreach ($content['FIELDS'] as $key => &$myField ) { // Set Fieldcaption @@ -273,6 +285,7 @@ if ( isset($_POST['op']) ) $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY']; } // --- +print_r ( $_POST ); if ( !isset($content['ISERROR']) ) { @@ -280,25 +293,62 @@ if ( isset($_POST['op']) ) if ( isset($_POST['subop']) ) { // Get NewColID - $szNewColID = DB_RemoveBadChars($_POST['newcolumn']); + $szColId = DB_RemoveBadChars($_POST['newcolumn']); // Add a new Column into our list! if ( $_POST['subop'] == $content['LN_VIEWS_ADDCOLUMN'] && isset($_POST['newcolumn']) ) { // Add New entry into columnlist - $content['COLUMNS'][$szNewColID]['ColFieldID'] = $szNewColID; + $content['SUBCOLUMNS'][$szColId]['ColFieldID'] = $szColId; + // Set Fieldcaption - if ( isset($content[ $fields[$szNewColID]['FieldCaptionID'] ]) ) - $content['COLUMNS'][$szNewColID]['ColCaption'] = $content[ $fields[$szNewColID]['FieldCaptionID'] ]; + if ( isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) + $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; else - $content['COLUMNS'][$szNewColID]['ColCaption'] = $szNewColID; + $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $szColId; // Set CSSClass - $content['COLUMNS'][$szNewColID]['colcssclass'] = count($content['COLUMNS']) % 2 == 0 ? "line1" : "line2"; + $content['SUBCOLUMNS'][$szColId]['colcssclass'] = count($content['SUBCOLUMNS']) % 2 == 0 ? "line1" : "line2"; + + // Remove from fields list as well + if ( isset($content['FIELDS'][$szColId]) ) + unset($content['FIELDS'][$szColId]); + } -// else if () -// { -// } + } + else if ( isset($_POST['subop_delete']) ) + { + // Get Column ID + $szColId = DB_RemoveBadChars($_POST['subop_delete']); + + // Remove Entry from Columnslist + if ( isset($content['SUBCOLUMNS'][$szColId]) ) + unset($content['SUBCOLUMNS'][$szColId]); + + // Add removed entry to field list + $content['FIELDS'][$szColId] = $fields[$szColId]; + + // Set Fieldcaption + if ( isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) + $content['FIELDS'][$szColId]['FieldCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; + else + $content['FIELDS'][$szColId]['FieldCaption'] = $szColId; + } + else if ( isset($_POST['subop_moveup']) ) + { + // Get Column ID + $szColId = DB_RemoveBadChars($_POST['subop_moveup']); + + // Move Entry one UP in Columnslist + + } + else if ( isset($_POST['subop_movedown']) ) + { + // Get Column ID + $szColId = DB_RemoveBadChars($_POST['subop_movedown']); + + // Move Entry one DOWN in Columnslist + } else // Now SUBOP means normal processing! { diff --git a/src/css/defaults.css b/src/css/defaults.css index ebddd41..9f49765 100644 --- a/src/css/defaults.css +++ b/src/css/defaults.css @@ -75,3 +75,11 @@ border:0px solid; background-color: transparent; } + +.borderlessbuttons +{ + border:0px solid; + background-color: transparent; + width:20px; + padding: 0px; +} diff --git a/src/images/icons/nav_down_blue.png b/src/images/icons/nav_down_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..17eef443cf604c035cb8d3125a16ad6c865df9e0 GIT binary patch literal 904 zcmV;319$w1P)WdKHUATc!{PH%P~GB7YQATcmHFgQ9gIUp-AF)%RoqGHAX000McNliru z)&&<0G9yY-1C#&&010qNS#tmY3lRVS3lRZ-WM7d0000DMK}|sb0I`n?{9y$E00Q7i zL_t(|+GSH+NK;`Le$F{NOZU^}rjnUkp=Cu9L@8Jh{ek7rT|^fJL4_AxL{St`QA7n% z&_yVPSWwWF-E@&qg2mWIKPr*V$aKy*r&IUq=623b@3+mZXz=X3JKy`f-}652I}U$; z&X1HJ6=*k?8(NB0Tn(2_BNmQ~Eljw3@vH9z2D+vxD@9ONC|sU=+uv+4rn~EF&1FeR z#*i{}DH+c1L7b^d zprt~@+mR>|vIIq?(m+>!1d72ApW`>N;!FvTv$nD-<8pVSMI*i*PGWqK6w=KPj#lB) z;WdDyWCgRQS`<;e{|0o{MtWml9q|dZ zGpq;N=)^)0ReF=QP2^H=jmNT%sOd{l3I$laL`Bm~1tCerP1|--w1wc|wQ6zWusec| zMjno1I-IPagm|)0fTZ7RuEx^CT?jnh4TC`os7&kWgh8j`+I)=T09^zw-XKB6o>=^{Io}VLL=-_-rE-1eqfOR&AJ+ z^y^NfIH8ESKk#!A-k6BCI(>G(dV{$B%t;rJgHf7NI2?rQ$siPIfR0@PX6(_FX8Qtk zrA>4x4@JheVLyV37It?Ex4InJ(K2#Uk}&wF2k|Knkv|cCKs47w;EUMY zor50qJIAxKmuV`INH9YVF}v}R@PiOrX_OTzh1R+UmHS#@s@s7gqXjY<8J+RKXLn() zF9bR6qdamFUkL&Be<(PWEyC8*O4yux8ggeeJ4_Hp3GUo)`CqFG)~nfqWdKHUATc!{PH%P~GB7YQATcmHFgQ9gIUp-AF)%RoqGHAX000McNliru z)&&<0G9=VCmNx(Z010qNS#tmY3lRVS3lRZ-WM7d0000DMK}|sb0I`n?{9y$E00QYr zL_t(|+D%hSNK|1I{_dR_bsje3jE|&zq!VV0hCPfT=(UkbH|<&kZ6YElLNuZX+z2gt z;4(~tT1Do+KU9jAHgydU?@z0|1N=pRqFW26Kb|$crar@*zwixX*LB)*ueGHyJBR!Fn<3dc}EC>6sE`qKk;_9m^H&^4;xD-9RhyX;;2q;qg9=O z)ssRpokm8f)Ie8x2^qZyuGv+JN=TU~aC54nrbhSWLbFl#ZkdPQpMnJeEc7%M$`AdF~t3GSTbH%A2bvydwYF9?w;BU z#3^Gg4QSLR)LrZ%Q!PQomKwEP#S9PIS`;meAJ(2mIkcb?URt=$E)v8nn7d>&bmBjF;*PIP_I_86z$?4lY;?M4nJ4_h5eT;ZnQqELQSM^x}M&#i0 zM)CY|72-(|FTVxQJK_Q>_h(?*jfbNF_+mhZMT;_xiu4yHlYvmAj0Ge1JqBItx!0?? zt|};X^-gz7l|cXddGvpvms2zX+ac1!y^*)GVdv&2CP@Yb38au_B%M=SUd2)>ERl+2 z<5Rh_Yify1K?29ZJ~tTH@CCC9)4(9XL7CYhP)TBPBpy!$F=~A%9E5v3Or~{0Oab>y zl-C^Dg08%SDqz87+C@YrEBJGiA5DNpN2+jM*|0`jZu$@;+8tHOq>&{@``A!&Hj-td+ z3xU#)x*c%MEW - + - + - + + - - + - - + - +
{LN_VIEWS_COLUMNLIST}{LN_VIEWS_COLUMNLIST}
- - {ColFieldID} {ColCaption} + {LN_VIEWS_COLUMN} {ZAEHLER}: + + {ColCaption} -   + + + + + +
+
{LN_GEN_USERONLY}{LN_GEN_USERONLY_LONG}
{LN_GEN_GROUPONLY}{LN_GEN_GROUPONLY_LONG} + + + + + + +
{LN_SOURCES_CENTER}
+

+ + + + + + + + + + + + + + + + + + + + + + +
{LN_SOURCES_ID}{LN_SOURCES_NAME}{LN_SOURCES_TYPE}{LN_SOURCES_ASSIGNTO}{LN_GEN_ACTIONS}
{ID}{Name} {SourcesTypeText} {SourcesAssignedToText} + +   +   + + +   +   + +
 {LN_SOURCES_ADD}
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_SEARCH_ADDEDIT}
{LN_SEARCH_NAME}
{LN_GEN_USERONLY}
{LN_GEN_USERONLY}
{LN_SEARCH_GROUPONLY} + +
+ + + +
+
+ + +

+ +
+ + \ No newline at end of file From 3b4c77a62f12be7405a417c4bc0133ddd4f4686c Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 22 Jul 2008 12:17:26 +0200 Subject: [PATCH 020/142] Adding Sources is possible now... --- src/admin/sources.php | 224 +++++++++++++++++++------ src/include/functions_config.php | 16 +- src/lang/en/admin.php | 8 +- src/lang/en/main.php | 2 + src/templates/admin/admin_sources.html | 153 +++++++++++++++-- 5 files changed, 332 insertions(+), 71 deletions(-) diff --git a/src/admin/sources.php b/src/admin/sources.php index 85cb841..986bcc6 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -54,25 +54,53 @@ IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); // --- BEGIN Custom Code -// Only if the user is an admin! -//if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) -// DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); - if ( isset($_GET['op']) ) { if ($_GET['op'] == "add") { // Set Mode to add - $content['ISEDITORNEWSEARCH'] = "true"; - $content['SEARCH_FORMACTION'] = "addnewsearch"; - $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_ADD']; + $content['ISEDITORNEWSOURCE'] = "true"; + $content['SOURCE_FORMACTION'] = "addnewsource"; + $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_ADD']; //PreInit these values - $content['DisplayName'] = ""; - $content['SearchQuery'] = ""; + $content['Name'] = ""; + $content['SourceType'] = SOURCE_DISK; + CreateSourceTypesList($content['SourceType']); + + // Init View List! + $content['SourceViewID'] = 'SYSLOG'; + $content['VIEWS'] = $content['Views']; + foreach ( $content['VIEWS'] as $myView ) + { + if ( $myView['ID'] == $content['SourceViewID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + } + + // SOURCE_DISK specific + $content['SourceLogLineType'] = ""; + CreateLogLineTypesList($content['SourceLogLineType']); + $content['SourceDiskFile'] = "/var/log/syslog"; + + // SOURCE_DB specific + $content['SourceDBType'] = DB_MYSQL; + CreateDBTypesList($content['SourceDBType']); + $content['SourceDBName'] = "phplogcon"; + $content['SourceDBTableType'] = "monitorware"; + $content['SourceDBServer'] = "localhost"; + $content['SourceDBTableName'] = "systemevents"; + $content['SourceDBUser'] = "user"; + $content['SourceDBPassword'] = ""; + $content['SourceDBEnableRowCounting'] = "false"; + $content['SourceDBEnableRowCounting_true'] = ""; + $content['SourceDBEnableRowCounting_false'] = "checked"; + + // General stuff $content['userid'] = null; $content['CHECKED_ISUSERONLY'] = ""; - $content['SEARCHID'] = ""; + $content['SOURCEID'] = ""; // --- Check if groups are available $content['SUBGROUPS'] = GetGroupsForSelectfield(); @@ -80,28 +108,6 @@ if ( isset($_GET['op']) ) $content['ISGROUPSAVAILABLE'] = true; else $content['ISGROUPSAVAILABLE'] = false; - - /* - $sqlquery = "SELECT " . - DB_GROUPS . ".ID as mygroupid, " . - DB_GROUPS . ".groupname " . - "FROM " . DB_GROUPS . - " ORDER BY " . DB_GROUPS . ".groupname"; - $result = DB_Query($sqlquery); - $content['SUBGROUPS'] = DB_GetAllRows($result, true); - if ( isset($content['SUBGROUPS']) && count($content['SUBGROUPS']) > 0 ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - $content['SUBGROUPS'][$i]['group_selected'] = ""; - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); - } - else - $content['ISGROUPSAVAILABLE'] = false;*/ - // --- } else if ($_GET['op'] == "edit") { @@ -240,9 +246,38 @@ if ( isset($_GET['op']) ) if ( isset($_POST['op']) ) { - if ( isset ($_POST['id']) ) { $content['SEARCHID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SEARCHID'] = -1; } - if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } - if ( isset ($_POST['SearchQuery']) ) { $content['SearchQuery'] = DB_RemoveBadChars($_POST['SearchQuery']); } else {$content['SearchQuery'] = ""; } + // Read parameters first! + if ( isset($_POST['id']) ) { $content['SOURCEID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SOURCEID'] = -1; } + if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } + if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } + if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } + + if ( isset($content['SourceType']) ) + { + // Disk Params + if ( $content['SourceType'] == SOURCE_DISK ) + { + if ( isset($_POST['SourceLogLineType']) ) { $content['SourceLogLineType'] = DB_RemoveBadChars($_POST['SourceLogLineType']); } + if ( isset($_POST['SourceDiskFile']) ) { $content['SourceDiskFile'] = DB_RemoveBadChars($_POST['SourceDiskFile']); } + } + // DB Params + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + if ( isset($_POST['SourceDBType']) ) { $content['SourceDBType'] = DB_RemoveBadChars($_POST['SourceDBType']); } + if ( isset($_POST['SourceDBName']) ) { $content['SourceDBName'] = DB_RemoveBadChars($_POST['SourceDBName']); } + if ( isset($_POST['SourceDBTableType']) ) { $content['SourceDBTableType'] = DB_RemoveBadChars($_POST['SourceDBTableType']); } + if ( isset($_POST['SourceDBServer']) ) { $content['SourceDBServer'] = DB_RemoveBadChars($_POST['SourceDBServer']); } + if ( isset($_POST['SourceDBTableName']) ) { $content['SourceDBTableName'] = DB_RemoveBadChars($_POST['SourceDBTableName']); } + if ( isset($_POST['SourceDBUser']) ) { $content['SourceDBUser'] = DB_RemoveBadChars($_POST['SourceDBUser']); } + if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } + if ( isset($_POST['SourceDBEnableRowCounting']) ) + { // Extra Check for this propberty + $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceViewID']); + if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) + $_SESSION['SourceDBEnableRowCounting'] = "false"; + } + } + } // User & Group handeled specially if ( isset ($_POST['isuseronly']) ) @@ -260,35 +295,127 @@ if ( isset($_POST['op']) ) } // --- Check mandotary values - if ( $content['DisplayName'] == "" ) + if ( $content['Name'] == "" ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY']; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_NAMEOFTHESOURCE'] ); } - else if ( $content['SearchQuery'] == "" ) + else if ( !isset($content['SourceType']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY']; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SOURCETYPE'] ); + } + else if ( !isset($content['SourceViewID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_VIEW'] ); + } + else + { + // Disk Params + if ( $content['SourceType'] == SOURCE_DISK ) + { + if ( !isset($content['SourceLogLineType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_LOGLINETYPE'] ); + } + else if ( !isset($content['SourceDiskFile']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SYSLOGFILE'] ); + } + // Check if file is accessable! + else if ( !is_file($content['SourceDiskFile']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $content['SourceDiskFile'] ); + } + } + // DB Params + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + if ( !isset($content['SourceDBType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DATABASETYPEOPTIONS'] ); + } + else if ( !isset($content['SourceDBName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBNAME'] ); + } + else if ( !isset($content['SourceDBTableType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLETYPE'] ); + } + else if ( !isset($content['SourceDBServer']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBSERVER'] ); + } + else if ( !isset($content['SourceDBTableName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLENAME'] ); + } + else if ( !isset($content['SourceDBUser']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBUSER'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'], $content['SourceDBType'] ); + } } - // --- + // --- Now ADD/EDIT do the processing! if ( !isset($content['ISERROR']) ) { // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewsearch" ) + if ( $_POST['op'] == "addnewsource" ) { // Add custom search now! - $sqlquery = "INSERT INTO " . DB_SEARCHES . " (DisplayName, SearchQuery, userid, groupid) - VALUES ('" . $content['DisplayName'] . "', - '" . $content['SearchQuery'] . "', - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; + if ( $content['SourceType'] == SOURCE_DISK ) + { + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, LogLineType, DiskFile, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['SourceType'] . ", + '" . $content['SourceViewID'] . "', + '" . $content['SourceLogLineType'] . "', + '" . $content['SourceDiskFile'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + } + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['SourceType'] . ", + '" . $content['SourceViewID'] . "', + '" . $content['SourceDBTableType'] . "', + " . $content['SourceDBType'] . ", + '" . $content['SourceDBServer'] . "', + '" . $content['SourceDBName'] . "', + '" . $content['SourceDBUser'] . "', + '" . $content['SourceDBPassword'] . "', + '" . $content['SourceDBTableName'] . "', + " . $content['SourceDBEnableRowCounting'] . ", + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + } + $result = DB_Query($sqlquery); DB_FreeQuery($result); // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENADDED'], $content['DisplayName'] ) , "searches.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCE_HASBEENADDED'], $content['Name'] ) , "sources.php" ); } else if ( $_POST['op'] == "editsearch" ) { @@ -384,7 +511,6 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) } // --- // print_r ( $content['SOURCES'] ); - } // --- END Custom Code diff --git a/src/include/functions_config.php b/src/include/functions_config.php index bedefd2..fcd9b76 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -100,7 +100,6 @@ function InitSourceConfigs() { // Perform necessary include require_once($gl_root_path . 'classes/logstreamconfigdisk.class.php'); - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDisk(); $content['Sources'][$iSourceID]['ObjRef']->FileName = $mysource['DiskFile']; $content['Sources'][$iSourceID]['ObjRef']->LineParserType = $mysource['LogLineType']; @@ -156,8 +155,8 @@ function InitSourceConfigs() // UNKNOWN, remove config entry! unset($content['Sources'][$iSourceID]); - // TODO: Output CONFIG WARNING - die( "Not supported yet!" ); + // Output CRITICAL WARNING + DieWithFriendlyErrorMsg( GetAndReplaceLangStr($content['LN_GEN_CRITERROR_UNKNOWNTYPE'], $mysource['SourceType']) ); } // Set generic configuration options @@ -446,9 +445,8 @@ function LoadSourcesFromDatabase() " WHERE (" . DB_SOURCES . ".userid IS NULL AND " . DB_SOURCES . ".groupid IS NULL) " . $szWhereUser . $szGroupWhere . - " ORDER BY " . DB_SOURCES . ".userid, " . DB_SOURCES . ".groupid, " . DB_SOURCES . ".DisplayName"; + " ORDER BY " . DB_SOURCES . ".userid, " . DB_SOURCES . ".groupid, " . DB_SOURCES . ".Name"; // --- - // Get Sources from DB now! $result = DB_Query($sqlquery); $myrows = DB_GetAllRows($result, true); @@ -456,12 +454,12 @@ function LoadSourcesFromDatabase() { // Overwrite existing Views array unset($CFG['Sources']); - - // Unpack the Columns and append to Views Array + + // Append to Source Array foreach ($myrows as &$mySource ) { - // Append to Views Array - $CFG['Sources'][ $mySource['ID'] ] = $mySource['ID']; + // Append to Source Array + $CFG['Sources'][ $mySource['ID'] ] = $mySource; //['ID']; } // Copy to content array! diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index a12040c..53d8e56 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -190,7 +190,13 @@ $content['LN_SOURCES_DISK'] = "Diskfile"; $content['LN_SOURCES_DB'] = "MySQL Database"; $content['LN_SOURCES_PDO'] = "PDO Datasource"; $content['LN_SOURCES_ADD'] = "Add new Source"; -$content['LN_SOURCES_'] = ""; +$content['LN_SOURCES_ADDEDIT'] = "Add / Edit a Source"; +$content['LN_SOURCES_TYPE'] = "Source Type"; +$content['LN_SOURCES_DISKTYPEOPTIONS'] = "Diskfile related Options"; +$content['LN_SOURCES_ERROR_MISSINGPARAM'] = "The paramater '%1' is missing."; +$content['LN_SOURCES_ERROR_NOTAVALIDFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it"; +$content['LN_SOURCES_ERROR_UNKNOWNSOURCE'] = "Unknown Source '%1' detected"; +$content['LN_SOURCE_HASBEENADDED'] = "The new Source '%1' has been successfully added."; $content['LN_SOURCES_'] = ""; $content['LN_SOURCES_'] = ""; $content['LN_SOURCES_'] = ""; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 8ce2d2d..80a9775 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -57,6 +57,8 @@ $content['LN_GEN_DB_FIREBIRD'] = "Firebird/Interbase 6"; $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; + $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index f0cce4d..bbe7386 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -1,5 +1,34 @@ + +

{ERROR_MSG}

@@ -47,19 +76,36 @@
- -
+ + + - + - - + + - - + + + + + + @@ -67,7 +113,7 @@ - + +
{LN_SEARCH_ADDEDIT}{LN_SOURCES_ADDEDIT}
{LN_SEARCH_NAME}{LN_SOURCES_NAME}
{LN_GEN_USERONLY}{LN_SOURCES_TYPE} + +
{LN_CFG_VIEW} + +
{LN_GEN_USERONLY}
{LN_SEARCH_GROUPONLY}{LN_GEN_GROUPONLY}
+
+ +
+ + + + + + + + + + +
{LN_SOURCES_DISKTYPEOPTIONS}
{LN_CFG_LOGLINETYPE} + +
{LN_CFG_SYSLOGFILE}
+
+ +
+ + +
{LN_CFG_DATABASETYPEOPTIONS}
+ +
+ + + + + +
{LN_CFG_DBSTORAGEENGINE} + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_CFG_DBTABLETYPE}
{LN_CFG_DBSERVER}
{LN_CFG_DBNAME}
{LN_CFG_DBTABLENAME}
{LN_CFG_DBUSER}
{LN_CFG_DBPASSWORD}
{LN_CFG_DBROWCOUNTING} + Yes No +
+
+ +
- - - + + +
- + + +

From 1e594e4037fe185bc481ebf8497eb38517d80ccc Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 22 Jul 2008 14:23:58 +0200 Subject: [PATCH 021/142] Adding, Editing, Deleting Sources does not fully work --- src/admin/searches.php | 50 ------- src/admin/sources.php | 184 +++++++++++++++---------- src/lang/en/admin.php | 8 +- src/templates/admin/admin_sources.html | 10 +- 4 files changed, 128 insertions(+), 124 deletions(-) diff --git a/src/admin/searches.php b/src/admin/searches.php index 63b2f63..daae8d2 100644 --- a/src/admin/searches.php +++ b/src/admin/searches.php @@ -80,28 +80,6 @@ if ( isset($_GET['op']) ) $content['ISGROUPSAVAILABLE'] = true; else $content['ISGROUPSAVAILABLE'] = false; - - /* - $sqlquery = "SELECT " . - DB_GROUPS . ".ID as mygroupid, " . - DB_GROUPS . ".groupname " . - "FROM " . DB_GROUPS . - " ORDER BY " . DB_GROUPS . ".groupname"; - $result = DB_Query($sqlquery); - $content['SUBGROUPS'] = DB_GetAllRows($result, true); - if ( isset($content['SUBGROUPS']) && count($content['SUBGROUPS']) > 0 ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - $content['SUBGROUPS'][$i]['group_selected'] = ""; - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); - } - else - $content['ISGROUPSAVAILABLE'] = false;*/ - // --- } else if ($_GET['op'] == "edit") { @@ -150,34 +128,6 @@ if ( isset($_GET['op']) ) else $content['ISGROUPSAVAILABLE'] = false; // --- -/* - // --- Check if groups are available - $sqlquery = "SELECT " . - DB_GROUPS . ".ID as mygroupid, " . - DB_GROUPS . ".groupname " . - "FROM " . DB_GROUPS . - " ORDER BY " . DB_GROUPS . ".groupname"; - $result = DB_Query($sqlquery); - $content['SUBGROUPS'] = DB_GetAllRows($result, true); - if ( isset($content['SUBGROUPS']) && count($content['SUBGROUPS']) > 0 ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - { - if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) - $content['SUBGROUPS'][$i]['group_selected'] = "selected"; - else - $content['SUBGROUPS'][$i]['group_selected'] = ""; - } - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); - } - else - $content['ISGROUPSAVAILABLE'] = false; - // --- -*/ } else { diff --git a/src/admin/sources.php b/src/admin/sources.php index 986bcc6..95895b7 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -112,27 +112,64 @@ if ( isset($_GET['op']) ) else if ($_GET['op'] == "edit") { // Set Mode to edit - $content['ISEDITORNEWSEARCH'] = "true"; - $content['SEARCH_FORMACTION'] = "editsearch"; - $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_EDIT']; + $content['ISEDITORNEWSOURCE'] = "true"; + $content['SOURCE_FORMACTION'] = "editsource"; + $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_EDIT']; if ( isset($_GET['id']) ) { //PreInit these values - $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); + $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); - $sqlquery = "SELECT * " . - " FROM " . DB_SEARCHES . - " WHERE ID = " . $content['SEARCHID']; - - $result = DB_Query($sqlquery); - $mysearch = DB_GetSingleRow($result, true); - if ( isset($mysearch['DisplayName']) ) + // Check if exists + if ( is_numeric($content['SOURCEID']) && isset($content['Sources'][ $content['SOURCEID'] ]) ) { - $content['SEARCHID'] = $mysearch['ID']; - $content['DisplayName'] = $mysearch['DisplayName']; - $content['SearchQuery'] = $mysearch['SearchQuery']; - if ( $mysearch['userid'] != null ) + // Get Source reference + $mysource = $content['Sources'][ $content['SOURCEID'] ]; + + // Copy basic properties + $content['Name'] = $mysource['Name']; + $content['SourceType'] = $mysource['SourceType']; + CreateSourceTypesList($content['SourceType']); + + // Init View List! + $content['SourceViewID'] = $mysource['ViewID']; + $content['VIEWS'] = $content['Views']; + foreach ( $content['VIEWS'] as $myView ) + { + if ( $myView['ID'] == $content['SourceViewID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + } + + // SOURCE_DISK specific + $content['SourceLogLineType'] = $mysource['LogLineType']; + CreateLogLineTypesList($content['SourceLogLineType']); + $content['SourceDiskFile'] = $mysource['DiskFile']; + + // SOURCE_DB specific + $content['SourceDBType'] = $mysource['DBType']; + CreateDBTypesList($content['SourceDBType']); + $content['SourceDBName'] = $mysource['DBName']; + $content['SourceDBTableType'] = $mysource['DBTableType']; + $content['SourceDBServer'] = $mysource['DBServer']; + $content['SourceDBTableName'] = $mysource['DBTableName']; + $content['SourceDBUser'] = $mysource['DBUser']; + $content['SourceDBPassword'] = $mysource['DBPassword']; + $content['SourceDBEnableRowCounting'] = $mysource['DBEnableRowCounting']; + if ( $content['SourceDBEnableRowCounting'] == 1 ) + { + $content['SourceDBEnableRowCounting_true'] = "checked"; + $content['SourceDBEnableRowCounting_false'] = ""; + } + else + { + $content['SourceDBEnableRowCounting_true'] = ""; + $content['SourceDBEnableRowCounting_false'] = "checked"; + } + + if ( $mysource['userid'] != null ) $content['CHECKED_ISUSERONLY'] = "checked"; else $content['CHECKED_ISUSERONLY'] = ""; @@ -144,7 +181,7 @@ if ( isset($_GET['op']) ) // Process All Groups for($i = 0; $i < count($content['SUBGROUPS']); $i++) { - if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) + if ( $mysource['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysource['groupid'] ) $content['SUBGROUPS'][$i]['group_selected'] = "selected"; else $content['SUBGROUPS'][$i]['group_selected'] = ""; @@ -156,40 +193,12 @@ if ( isset($_GET['op']) ) else $content['ISGROUPSAVAILABLE'] = false; // --- -/* - // --- Check if groups are available - $sqlquery = "SELECT " . - DB_GROUPS . ".ID as mygroupid, " . - DB_GROUPS . ".groupname " . - "FROM " . DB_GROUPS . - " ORDER BY " . DB_GROUPS . ".groupname"; - $result = DB_Query($sqlquery); - $content['SUBGROUPS'] = DB_GetAllRows($result, true); - if ( isset($content['SUBGROUPS']) && count($content['SUBGROUPS']) > 0 ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - { - if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) - $content['SUBGROUPS'][$i]['group_selected'] = "selected"; - else - $content['SUBGROUPS'][$i]['group_selected'] = ""; - } - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - array_unshift( $content['SUBGROUPS'], array ("mygroupid" => -1, "groupname" => $content['LN_SEARCH_SELGROUPENABLE'], "group_selected" => "") ); - } - else - $content['ISGROUPSAVAILABLE'] = false; - // --- -*/ } else { - $content['ISEDITORNEWSEARCH'] = false; + $content['ISEDITORNEWSOURCE'] = false; $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; } } else @@ -204,42 +213,42 @@ if ( isset($_GET['op']) ) if ( isset($_GET['id']) ) { //PreInit these values - $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); + $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); // Get UserInfo - $result = DB_Query("SELECT DisplayName FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); + $result = DB_Query("SELECT Name FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['DisplayName']) ) + if ( !isset($myrow['Name']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); } // --- Ask for deletion first! if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) { // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SEARCH_WARNDELETESEARCH'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SOURCES_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); } // --- // do the delete! - $result = DB_Query( "DELETE FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); + $result = DB_Query( "DELETE FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); if ($result == FALSE) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_DELSEARCH'], $content['SEARCHID'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_DELSOURCE'], $content['SOURCEID'] ); } else DB_FreeQuery($result); // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "searches.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_HASBEENDEL'], $myrow['Name'] ) , "sources.php" ); } else { $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; } } } @@ -270,12 +279,11 @@ if ( isset($_POST['op']) ) if ( isset($_POST['SourceDBTableName']) ) { $content['SourceDBTableName'] = DB_RemoveBadChars($_POST['SourceDBTableName']); } if ( isset($_POST['SourceDBUser']) ) { $content['SourceDBUser'] = DB_RemoveBadChars($_POST['SourceDBUser']); } if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } - if ( isset($_POST['SourceDBEnableRowCounting']) ) - { // Extra Check for this propberty - $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceViewID']); - if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) - $_SESSION['SourceDBEnableRowCounting'] = "false"; - } + if ( isset($_POST['SourceDBEnableRowCounting']) ) { $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceDBEnableRowCounting']); } + // Extra Check for this property + if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) + $_SESSION['SourceDBEnableRowCounting'] = "false"; + } } @@ -413,32 +421,58 @@ if ( isset($_POST['op']) ) $result = DB_Query($sqlquery); DB_FreeQuery($result); - + // Do the final redirect RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCE_HASBEENADDED'], $content['Name'] ) , "sources.php" ); } - else if ( $_POST['op'] == "editsearch" ) + else if ( $_POST['op'] == "editsource" ) { - $result = DB_Query("SELECT ID FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID']); + $result = DB_Query("SELECT ID FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID']); $myrow = DB_GetSingleRow($result, true); if ( !isset($myrow['ID']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); } else { // Edit the Search Entry now! - $result = DB_Query("UPDATE " . DB_SEARCHES . " SET - DisplayName = '" . $content['DisplayName'] . "', - SearchQuery = '" . $content['SearchQuery'] . "', - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['SEARCHID']); + if ( $content['SourceType'] == SOURCE_DISK ) + { + $sqlquery = "UPDATE " . DB_SOURCES . " SET + Name = '" . $content['Name'] . "', + SourceType = " . $content['SourceType'] . ", + ViewID = '" . $content['SourceViewID'] . "', + LogLineType = '" . $content['SourceLogLineType'] . "', + DiskFile = '" . $content['SourceDiskFile'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SOURCEID']; + } + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + $sqlquery = "UPDATE " . DB_SOURCES . " SET + Name = '" . $content['Name'] . "', + SourceType = " . $content['SourceType'] . ", + ViewID = '" . $content['SourceViewID'] . "', + DBTableType = '" . $content['SourceDBTableType'] . "', + DBType = " . $content['SourceDBType'] . ", + DBServer = '" . $content['SourceDBServer'] . "', + DBName = '" . $content['SourceDBName'] . "', + DBUser = '" . $content['SourceDBUser'] . "', + DBPassword = '" . $content['SourceDBPassword'] . "', + DBTableName = '" . $content['SourceDBTableName'] . "', + DBEnableRowCounting = " . $content['SourceDBEnableRowCounting'] . ", + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SOURCEID']; + } + + $result = DB_Query($sqlquery); DB_FreeQuery($result); // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENEDIT'], $content['DisplayName']) , "searches.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_HASBEENEDIT'], $content['Name']) , "sources.php" ); } } } @@ -460,6 +494,9 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // NonNUMERIC are config files Sources, can not be editied if ( is_numeric($mySource['ID']) ) { + // Allow EDIT + $mySource['ActionsAllowed'] = true; + if ( $mySource['userid'] != null ) { $mySource['SourcesAssignedToImage'] = $content["MENU_ADMINUSERS"]; @@ -478,6 +515,9 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) } else { + // Disallow EDIT + $mySource['ActionsAllowed'] = false; + $mySource['SourcesAssignedToImage'] = $content["MENU_INTERNAL"]; $mySource['SourcesAssignedToText'] = $content["LN_GEN_CONFIGFILE"]; } diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 53d8e56..2bf3fa9 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -197,7 +197,13 @@ $content['LN_SOURCES_ERROR_MISSINGPARAM'] = "The paramater '%1' is missing."; $content['LN_SOURCES_ERROR_NOTAVALIDFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it"; $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'] = "Unknown Source '%1' detected"; $content['LN_SOURCE_HASBEENADDED'] = "The new Source '%1' has been successfully added."; -$content['LN_SOURCES_'] = ""; +$content['LN_SOURCES_EDIT'] = "Edit Source"; +$content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID'] = "The Source-ID is invalid or could not be found."; +$content['LN_SOURCES_ERROR_IDNOTFOUND'] = "The Source-ID could not be found in the database."; +$content['LN_SOURCES_HASBEENEDIT'] = "The Source '%1' has been successfully edited."; +$content['LN_SOURCES_WARNDELETESEARCH'] = "Are you sure that you want to delete the Source '%1'? This cannot be undone!"; +$content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' failed!"; +$content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; $content['LN_SOURCES_'] = ""; $content['LN_SOURCES_'] = ""; diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index bbe7386..7a6e2e2 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -55,7 +55,15 @@
{ID}{Name} + + {Name} + + + {Name} + + + {SourcesTypeText} {SourcesAssignedToText} From 662f7e36358a6f2c477ed791205a24a0087f3f7f Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 22 Jul 2008 16:10:04 +0200 Subject: [PATCH 022/142] Normal Users can not edit group or global sources, views or searches anymore. --- src/admin/searches.php | 64 ++++++++----------- src/admin/sources.php | 12 +++- src/admin/views.php | 82 +++++-------------------- src/include/db_template.txt | 33 ++++++---- src/include/functions_config.php | 25 ++++---- src/lang/en/admin.php | 2 + src/lang/pt_BR/main.php | 1 + src/templates/admin/admin_searches.html | 20 ++++-- src/templates/admin/admin_sources.html | 4 +- src/templates/admin/admin_views.html | 17 +++-- 10 files changed, 121 insertions(+), 139 deletions(-) diff --git a/src/admin/searches.php b/src/admin/searches.php index daae8d2..7e3128e 100644 --- a/src/admin/searches.php +++ b/src/admin/searches.php @@ -53,11 +53,6 @@ IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); // --- // --- BEGIN Custom Code - -// Only if the user is an admin! -//if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) -// DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); - if ( isset($_GET['op']) ) { if ($_GET['op'] == "add") @@ -272,54 +267,49 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // Default Mode = List Searches $content['LISTSEARCHES'] = "true"; - // Read all Serverentries - $sqlquery = "SELECT " . - DB_SEARCHES . ".ID, " . - DB_SEARCHES . ".DisplayName, " . - DB_SEARCHES . ".SearchQuery, " . - DB_SEARCHES . ".userid, " . - DB_SEARCHES . ".groupid, " . - DB_USERS . ".username, " . - DB_GROUPS . ".groupname " . - " FROM " . DB_SEARCHES . - " LEFT OUTER JOIN (" . DB_USERS . ", " . DB_GROUPS . - ") ON (" . - DB_SEARCHES . ".userid=" . DB_USERS . ".ID AND " . - DB_SEARCHES . ".groupid=" . DB_GROUPS . ".ID " . - ") " . - " ORDER BY " . DB_SEARCHES . ".userid, " . DB_SEARCHES . ".groupid, " . DB_SEARCHES . ".DisplayName"; -//echo $sqlquery; - $result = DB_Query($sqlquery); - $content['SEARCHES'] = DB_GetAllRows($result, true); + // Copy Search array for further modifications + $content['SEARCHES'] = $content['Search']; - // --- Process Users - for($i = 0; $i < count($content['SEARCHES']); $i++) + $i = 0; // Help counter! + foreach ($content['SEARCHES'] as &$mySearch ) { - $content['SEARCHES'][$i]['SearchQuery_Display'] = strlen($content['SEARCHES'][$i]['SearchQuery']) > 25 ? substr($content['SEARCHES'][$i]['SearchQuery'], 0, 25) . " ..." : $content['SEARCHES'][$i]['SearchQuery']; + $mySearch['SearchQuery_Display'] = strlen($mySearch['SearchQuery']) > 25 ? substr($mySearch['SearchQuery'], 0, 25) . " ..." : $mySearch['SearchQuery']; + + // Allow EDIT + $mySearch['ActionsAllowed'] = true; // --- Set Image for Type - if ( $content['SEARCHES'][$i]['userid'] != null ) + if ( $mySearch['userid'] != null ) { - $content['SEARCHES'][$i]['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; - $content['SEARCHES'][$i]['SearchTypeText'] = $content["LN_GEN_USERONLY"]; + $mySearch['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; + $mySearch['SearchTypeText'] = $content["LN_GEN_USERONLY"]; } - else if ( $content['SEARCHES'][$i]['groupid'] != null ) + else if ( $mySearch['groupid'] != null ) { - $content['SEARCHES'][$i]['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; - $content['SEARCHES'][$i]['SearchTypeText'] = $content["LN_GEN_GROUPONLY"]; + $mySearch['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; + $mySearch['SearchTypeText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $mySearch['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySearch['ActionsAllowed'] = false; } else { - $content['SEARCHES'][$i]['SearchTypeImage'] = $content["MENU_GLOBAL"]; - $content['SEARCHES'][$i]['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; + $mySearch['SearchTypeImage'] = $content["MENU_GLOBAL"]; + $mySearch['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySearch['ActionsAllowed'] = false; } // --- // --- Set CSS Class if ( $i % 2 == 0 ) - $content['SEARCHES'][$i]['cssclass'] = "line1"; + $mySearch['cssclass'] = "line1"; else - $content['SEARCHES'][$i]['cssclass'] = "line2"; + $mySearch['cssclass'] = "line2"; + $i++; // --- } // --- diff --git a/src/admin/sources.php b/src/admin/sources.php index 95895b7..656fbea 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -483,7 +483,7 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // Default Mode = List Searches $content['LISTSOURCES'] = "true"; - // Copy Views array for further modifications + // Copy Sources array for further modifications $content['SOURCES'] = $content['Sources']; // --- Process Sources @@ -505,12 +505,20 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) else if ( $mySource['groupid'] != null ) { $mySource['SourcesAssignedToImage'] = $content["MENU_ADMINGROUPS"]; - $mySource['SourcesAssignedToText'] = $content["LN_GEN_GROUPONLY"]; + $mySource['SourcesAssignedToText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $mySource['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySource['ActionsAllowed'] = false; } else { $mySource['SourcesAssignedToImage'] = $content["MENU_GLOBAL"]; $mySource['SourcesAssignedToText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySource['ActionsAllowed'] = false; } } else diff --git a/src/admin/views.php b/src/admin/views.php index dd1196b..eb50457 100644 --- a/src/admin/views.php +++ b/src/admin/views.php @@ -91,23 +91,12 @@ if ( isset($_GET['op']) ) // Copy Views array for further modifications $content['VIEWS'] = $content['Views']; -//print_r ( $content['VIEWS'] ); + // View must be loaded as well already! if ( isset($_GET['id']) && isset($content['VIEWS'][$_GET['id']]) ) { //PreInit these values $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); - -/* - $sqlquery = "SELECT ID, DisplayName " . - " FROM " . DB_VIEWS . - " WHERE ID = " . $content['VIEWID']; - - $result = DB_Query($sqlquery); - $myview = DB_GetSingleRow($result, true); - if ( isset($myview['DisplayName']) ) -*/ - if ( isset($content['VIEWS'][ $content['VIEWID'] ]) ) { $myview = $content['VIEWS'][ $content['VIEWID'] ]; @@ -120,29 +109,6 @@ if ( isset($_GET['op']) ) else $content['CHECKED_ISUSERONLY'] = ""; -/* - // --- Add DisplayNames to columns - $iBegin = true; - foreach ($myview['Columns'] as $myCol ) - { - // Get Fieldcaption - if ( isset($content[ $fields[$myCol]['FieldCaptionID'] ]) ) - $myview['COLUMNS'][$myCol]['FieldCaption'] = $content[ $fields[$myCol]['FieldCaptionID'] ]; - else - $myview['COLUMNS'][$myCol]['FieldCaption'] = $myCol; - - if ( $iBegin ) - { - $myview['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ""; - $iBegin = false; - } - else - $myview['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ", "; - - } - // --- -*/ - // --- Check if groups are available $content['SUBGROUPS'] = GetGroupsForSelectfield(); if ( is_array($content['SUBGROUPS']) ) @@ -321,7 +287,6 @@ if ( isset($_POST['op']) ) $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY']; } // --- -//print_r ( $_POST ); if ( !isset($content['ISERROR']) ) { @@ -518,27 +483,6 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) { // Default Mode = List Searches $content['LISTVIEWS'] = "true"; -/* - // Read all Serverentries - $sqlquery = "SELECT " . - DB_VIEWS . ".ID, " . - DB_VIEWS . ".DisplayName, " . - DB_VIEWS . ".Columns, " . - DB_VIEWS . ".userid, " . - DB_VIEWS . ".groupid, " . - DB_USERS . ".username, " . - DB_GROUPS . ".groupname " . - " FROM " . DB_VIEWS . - " LEFT OUTER JOIN (" . DB_USERS . ", " . DB_GROUPS . - ") ON (" . - DB_VIEWS . ".userid=" . DB_USERS . ".ID AND " . - DB_VIEWS . ".groupid=" . DB_GROUPS . ".ID " . - ") " . - " ORDER BY " . DB_VIEWS . ".userid, " . DB_VIEWS . ".groupid, " . DB_VIEWS . ".DisplayName"; -//echo $sqlquery; - $result = DB_Query($sqlquery); - $content['VIEWS'] = DB_GetAllRows($result, true); -*/ // Copy Views array for further modifications $content['VIEWS'] = $content['Views']; @@ -555,18 +499,26 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // --- Set Image for Type if ( $myView['userid'] != null ) { - $myView['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; - $myView['SearchTypeText'] = $content["LN_GEN_USERONLY"]; + $myView['ViewTypeImage'] = $content["MENU_ADMINUSERS"]; + $myView['ViewTypeText'] = $content["LN_GEN_USERONLY"]; } else if ( $myView['groupid'] != null ) { - $myView['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; - $myView['SearchTypeText'] = $content["LN_GEN_GROUPONLY"]; + $myView['ViewTypeImage'] = $content["MENU_ADMINGROUPS"]; + $myView['ViewTypeText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $myView['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $myView['ActionsAllowed'] = false; } else { - $myView['SearchTypeImage'] = $content["MENU_GLOBAL"]; - $myView['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; + $myView['ViewTypeImage'] = $content["MENU_GLOBAL"]; + $myView['ViewTypeText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $myView['ActionsAllowed'] = false; } // --- } @@ -574,8 +526,8 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) { $myView['ActionsAllowed'] = false; - $myView['SearchTypeImage'] = $content["MENU_INTERNAL"]; - $myView['SearchTypeText'] = $content["LN_GEN_INTERNAL"]; + $myView['ViewTypeImage'] = $content["MENU_INTERNAL"]; + $myView['ViewTypeText'] = $content["LN_GEN_INTERNAL"]; } // --- Add DisplayNames to columns diff --git a/src/include/db_template.txt b/src/include/db_template.txt index a432dfd..b146a33 100644 --- a/src/include/db_template.txt +++ b/src/include/db_template.txt @@ -1,12 +1,13 @@ --- MYSQL Table Template --- for phpLogCon Version 0 --- http://www.phplogcon.org +-- phpMyAdmin SQL Dump +-- version 2.10.1 +-- http://www.phpmyadmin.net SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- Table structure for table `logcon_config` -- + DROP TABLE IF EXISTS `logcon_config`; CREATE TABLE IF NOT EXISTS `logcon_config` ( `propname` varchar(32) NOT NULL, @@ -19,6 +20,7 @@ CREATE TABLE IF NOT EXISTS `logcon_config` ( -- -- Table structure for table `logcon_groupmembers` -- + DROP TABLE IF EXISTS `logcon_groupmembers`; CREATE TABLE IF NOT EXISTS `logcon_groupmembers` ( `userid` int(11) NOT NULL, @@ -29,6 +31,7 @@ CREATE TABLE IF NOT EXISTS `logcon_groupmembers` ( -- -- Table structure for table `logcon_groups` -- + DROP TABLE IF EXISTS `logcon_groups`; CREATE TABLE IF NOT EXISTS `logcon_groups` ( `ID` int(11) NOT NULL auto_increment, @@ -36,28 +39,30 @@ CREATE TABLE IF NOT EXISTS `logcon_groups` ( `groupdescription` varchar(255) NOT NULL, `grouptype` int(11) NOT NULL, PRIMARY KEY (`ID`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table for phplogcon groups' AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table for phplogcon groups' AUTO_INCREMENT=1 ; -- -- Table structure for table `logcon_searches` -- + DROP TABLE IF EXISTS `logcon_searches`; CREATE TABLE IF NOT EXISTS `logcon_searches` ( `ID` int(11) NOT NULL auto_increment, `DisplayName` varchar(255) NOT NULL, `SearchQuery` varchar(1024) NOT NULL, `userid` int(11) default NULL, - `groupidd` int(11) default NULL, + `groupid` int(11) default NULL, PRIMARY KEY (`ID`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Stores custom user searches' AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Stores custom user searches' AUTO_INCREMENT=1 ; -- -- Table structure for table `logcon_sources` -- + DROP TABLE IF EXISTS `logcon_sources`; CREATE TABLE IF NOT EXISTS `logcon_sources` ( - `ID` varchar(64) NOT NULL, - `DisplayName` varchar(255) NOT NULL, + `ID` int(11) NOT NULL auto_increment, + `Name` varchar(255) NOT NULL, `SourceType` tinyint(4) NOT NULL, `ViewID` varchar(64) NOT NULL, `LogLineType` varchar(64) default NULL, @@ -73,30 +78,32 @@ CREATE TABLE IF NOT EXISTS `logcon_sources` ( `userid` int(11) default NULL, `groupid` int(11) default NULL, PRIMARY KEY (`ID`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table to store datasources in phplogcon'; +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table to store datasources in phplogcon' AUTO_INCREMENT=1 ; -- -- Table structure for table `logcon_users` -- + DROP TABLE IF EXISTS `logcon_users`; CREATE TABLE IF NOT EXISTS `logcon_users` ( `ID` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL, `password` varchar(32) NOT NULL, `is_admin` tinyint(1) NOT NULL default '0', - `last_login` tinyint(4) NOT NULL, + `last_login` int(4) NOT NULL, PRIMARY KEY (`ID`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table for the phplogcon users' AUTO_INCREMENT=2 ; +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Table for the phplogcon users' AUTO_INCREMENT=1 ; -- -- Table structure for table `logcon_views` -- + DROP TABLE IF EXISTS `logcon_views`; CREATE TABLE IF NOT EXISTS `logcon_views` ( - `ID` varchar(64) NOT NULL, + `ID` int(11) NOT NULL auto_increment, `DisplayName` varchar(255) NOT NULL, `Columns` text NOT NULL, `userid` int(11) default NULL, `groupid` int(11) default NULL, PRIMARY KEY (`ID`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Stores custom defined user views.'; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Stores custom defined user views.' AUTO_INCREMENT=1 ; diff --git a/src/include/functions_config.php b/src/include/functions_config.php index fcd9b76..632aafe 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -327,8 +327,17 @@ function LoadSearchesFromDatabase() $szGroupWhere = " OR " . DB_SEARCHES . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ")"; else $szGroupWhere = ""; - $sqlquery = " SELECT * " . + $sqlquery = " SELECT " . + DB_SEARCHES . ".ID, " . + DB_SEARCHES . ".DisplayName, " . + DB_SEARCHES . ".SearchQuery, " . + DB_SEARCHES . ".userid, " . + DB_SEARCHES . ".groupid, " . + DB_USERS . ".username, " . + DB_GROUPS . ".groupname " . " FROM " . DB_SEARCHES . + " LEFT OUTER JOIN (" . DB_USERS . ") ON (" . DB_SEARCHES . ".userid=" . DB_USERS . ".ID ) " . + " LEFT OUTER JOIN (" . DB_GROUPS . ") ON (" . DB_SEARCHES . ".groupid=" . DB_GROUPS . ".ID ) " . " WHERE (" . DB_SEARCHES . ".userid IS NULL AND " . DB_SEARCHES . ".groupid IS NULL) " . $szWhereUser . $szGroupWhere . @@ -371,11 +380,8 @@ function LoadViewsFromDatabase() DB_USERS . ".username, " . DB_GROUPS . ".groupname " . " FROM " . DB_VIEWS . - " LEFT OUTER JOIN (" . DB_USERS . ", " . DB_GROUPS . - ") ON (" . - DB_VIEWS . ".userid=" . DB_USERS . ".ID AND " . - DB_VIEWS . ".groupid=" . DB_GROUPS . ".ID " . - ") " . + " LEFT OUTER JOIN (" . DB_USERS . ") ON (" . DB_VIEWS . ".userid=" . DB_USERS . ".ID ) " . + " LEFT OUTER JOIN (" . DB_GROUPS . ") ON (" . DB_VIEWS . ".groupid=" . DB_GROUPS . ".ID ) " . " WHERE (" . DB_VIEWS . ".userid IS NULL AND " . DB_VIEWS . ".groupid IS NULL) " . $szWhereUser . $szGroupWhere . @@ -437,11 +443,8 @@ function LoadSourcesFromDatabase() DB_USERS . ".username, " . DB_GROUPS . ".groupname " . " FROM " . DB_SOURCES . - " LEFT OUTER JOIN (" . DB_USERS . ", " . DB_GROUPS . - ") ON (" . - DB_SOURCES . ".userid=" . DB_USERS . ".ID AND " . - DB_SOURCES . ".groupid=" . DB_GROUPS . ".ID " . - ") " . + " LEFT OUTER JOIN (" . DB_USERS . ") ON (" . DB_SOURCES . ".userid=" . DB_USERS . ".ID ) " . + " LEFT OUTER JOIN (" . DB_GROUPS . ") ON (" . DB_SOURCES . ".groupid=" . DB_GROUPS . ".ID ) " . " WHERE (" . DB_SOURCES . ".userid IS NULL AND " . DB_SOURCES . ".groupid IS NULL) " . $szWhereUser . $szGroupWhere . diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 2bf3fa9..3d0245a 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -47,6 +47,8 @@ $content['LN_GEN_GROUPONLY'] = "Group only"; $content['LN_GEN_GLOBAL'] = "Global"; $content['LN_GEN_USERONLY_LONG'] = "For me only
(Only available to your user)"; $content['LN_GEN_GROUPONLY_LONG'] = "For this group
(Only available to the selected group)"; +$content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; + // General Options $content['LN_ADMIN_MISC'] = "Miscellaneous Options"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index d241e4c..167efc3 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -60,6 +60,7 @@ $content['LN_GEN_DB_FIREBIRD'] = "Firebird/Interbase 6"; $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Visão"; + $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/admin/admin_searches.html b/src/templates/admin/admin_searches.html index 9abae0c..a3d1769 100644 --- a/src/templates/admin/admin_searches.html +++ b/src/templates/admin/admin_searches.html @@ -20,20 +20,32 @@
{LN_SEARCH_ID} {LN_SEARCH_NAME} {LN_SEARCH_QUERY}{LN_SEARCH_TYPE}{LN_GEN_ACTIONS}{LN_SEARCH_TYPE}{LN_GEN_ACTIONS}
{ID}{DisplayName} + + {DisplayName} + + + {DisplayName} + + {SearchQuery_Display} {SearchTypeText} +     + + +   +   +
{LN_SOURCES_ID} {LN_SOURCES_NAME} {LN_SOURCES_TYPE}{LN_SOURCES_ASSIGNTO}{LN_GEN_ACTIONS}{LN_SOURCES_ASSIGNTO}{LN_GEN_ACTIONS}
{LN_VIEWS_ID} {LN_VIEWS_NAME}{LN_VIEWS_COLUMNS}{LN_VIEWS_TYPE}{LN_GEN_ACTIONS}{LN_VIEWS_COLUMNS}{LN_VIEWS_TYPE}{LN_GEN_ACTIONS}
{ID}{DisplayName} + + {DisplayName} + + + {DisplayName} + + {FieldCaptionSeperator}{FieldCaption} {SearchTypeText} {ViewTypeText}   From 4f339b3e0e467c86b8f6b9e9c719c9ff9d224991 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 22 Jul 2008 16:57:36 +0200 Subject: [PATCH 023/142] Global options can not be edited by non admin users anymore. --- src/admin/index.php | 18 +++++++++--------- src/lang/en/admin.php | 5 +++-- src/templates/admin/admin_index.html | 9 +++++++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index 33507f2..b804850 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -55,15 +55,17 @@ InitFilterHelpers(); // Helpers for frontend filtering! // Init admin langauge file now! IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); -// --- Define Extra Stylesheet! -//$content['EXTRA_STYLESHEET'] = '' . "\r\n"; -//$content['EXTRA_STYLESHEET'] .= ''; -// --- - // --- BEGIN Custom Code -// Check for changes first -if ( isset($_POST['op']) ) +if ( isset($_SESSION['SESSION_ISADMIN']) && $_SESSION['SESSION_ISADMIN'] == 1 ) + $content['EditAllowed'] = true; +else + $content['EditAllowed'] = false; + + + +// Check for changes first | Abort if Edit is not allowed +if ( isset($_POST['op']) && $content['EditAllowed'] ) { if ( $_POST['op'] == "edit" ) { @@ -135,8 +137,6 @@ if ($content['MiscShowDebugGridCounter'] == 1) { $content['MiscShowDebugGridCoun if ($content['MiscShowPageRenderStats'] == 1) { $content['MiscShowPageRenderStats_checked'] = "checked"; } else { $content['MiscShowPageRenderStats_checked'] = ""; } if ($content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompression_checked'] = "checked"; } else { $content['MiscEnableGzipCompression_checked'] = ""; } if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } - - // --- // --- BEGIN CREATE TITLE diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 3d0245a..364021f 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -51,13 +51,14 @@ $content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; // General Options +$content['LN_ADMIN_GLOBFRONTEND'] = "Global frontend options"; +$content['LN_ADMIN_USERFRONTEND'] = "User specific frontend options"; $content['LN_ADMIN_MISC'] = "Miscellaneous Options"; $content['LN_GEN_SHOWDEBUGMSG'] = "Show Debug messages"; $content['LN_GEN_DEBUGGRIDCOUNTER'] = "Show Debug Gridcounter"; $content['LN_GEN_SHOWPAGERENDERSTATS'] = "Show Pagerenderstats"; $content['LN_GEN_ENABLEGZIP'] = "Enable GZIP Compressed Output"; $content['LN_GEN_DEBUGUSERLOGIN'] = "Debug Userlogin"; -$content['LN_ADMIN_FRONTEND'] = "Frontend Options"; $content['LN_GEN_WEBSTYLE'] = "Default selected style"; $content['LN_GEN_SELLANGUAGE'] = "Default selected language"; $content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; @@ -73,7 +74,7 @@ $content['LN_GEN_SUCCESSFULLYSAVED'] = "The configuration Values have been succe $content['LN_GEN_INTERNAL'] = "Internal"; $content['LN_GEN_DISABLED'] = "Function disabled"; $content['LN_GEN_CONFIGFILE'] = "Configuration File"; -$content['LN_GEN_'] = ""; +$content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 78c9bac..7f3e525 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -12,7 +12,7 @@ + {LN_ADMIN_GLOBFRONTEND} @@ -95,11 +95,16 @@ -
- {LN_ADMIN_FRONTEND}
{LN_GEN_WEBSTYLE}{LN_GEN_DEBUGUSERLOGIN}
+ + + + + +
From 1f5365803a7548f28de0eac14ddceb320af5cd48 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 22 Jul 2008 17:34:47 +0200 Subject: [PATCH 024/142] Started adding the conversion script for the configuration. --- src/convert.php | 148 +++++++++++++ src/include/functions_common.php | 2 +- src/install.php | 2 +- src/lang/en/main.php | 6 +- src/templates/convert.html | 343 +++++++++++++++++++++++++++++++ 5 files changed, 498 insertions(+), 3 deletions(-) create mode 100644 src/convert.php create mode 100644 src/templates/convert.html diff --git a/src/convert.php b/src/convert.php new file mode 100644 index 0000000..ca3cfad --- /dev/null +++ b/src/convert.php @@ -0,0 +1,148 @@ + Helps to convert from config file to userdb if desired + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd + +// --- PreCheck if conversion is allowed! +if ( + (isset($CFG['UserDBEnabled']) && $CFG['UserDBEnabled']) && + (isset($CFG['UserDBConvertAllowed']) && $CFG['UserDBConvertAllowed']) + ) +{ + // Setup static values + define('MAX_STEPS', 6); + $content['web_theme'] = "default"; + $content['user_theme'] = "default"; +} +else + DieWithErrorMsg( 'phpLogCon is not allowed to convert your settings into the user database.

If you want to convert your convert your settings, add the variable following into your config.php:
$CFG[\'UserDBConvertAllowed\'] = true;

Click here to return to pgpLogCon start page.'); +// --- + +// --- CONTENT Vars +$content['TITLE'] = "phpLogCon :: " . $content['LN_CONVERT_TITLE']; +// --- + +// --- Read Vars +if ( isset($_GET['step']) ) +{ + $content['CONVERT_STEP'] = intval(DB_RemoveBadChars($_GET['step'])); + if ( $content['CONVERT_STEP'] > MAX_STEPS ) + $content['CONVERT_STEP'] = 1; +} +else + $content['CONVERT_STEP'] = 1; + +// Set Next Step +$content['CONVERT_NEXT_STEP'] = $content['CONVERT_STEP']; + +if ( MAX_STEPS > $content['CONVERT_STEP'] ) +{ + $content['NEXT_ENABLED'] = "true"; + $content['FINISH_ENABLED'] = "false"; + $content['CONVERT_NEXT_STEP']++; +} +else +{ + $content['NEXT_ENABLED'] = "false"; + $content['FINISH_ENABLED'] = "true"; +} +// --- + +// --- BEGIN Custom Code + +// Set Bar Images +$content['BarImagePlus'] = $gl_root_path . "images/bars/bar-middle/green_middle_17.png"; +$content['BarImageLeft'] = $gl_root_path . "images/bars/bar-middle/green_left_17.png"; +$content['BarImageRight'] = $gl_root_path . "images/bars/bar-middle/green_right_17.png"; +$content['WidthPlus'] = intval( $content['CONVERT_STEP'] * (100 / MAX_STEPS) ) - 8; +$content['WidthPlusText'] = "Installer Step " . $content['CONVERT_STEP']; + +// --- Set Title +$content['TITLE'] = GetAndReplaceLangStr( $content['TITLE'], $content['CONVERT_STEP'] ); +// --- + +// --- Start Setup Processing +if ( $content['CONVERT_STEP'] == 2 ) +{ + // Check the database connect + $link_id = mysql_connect( $CFG['UserDBServer'], $CFG['UserDBUser'], $CFG['UserDBPass']); + if (!$link_id) + RevertOneStep( $content['INSTALL_STEP']-1, "Connect to " .$CFG['UserDBServer'] . " failed! Check Servername, Port, User and Password!
" . DB_ReturnSimpleErrorMsg() ); + + // Try to select the DB! + $db_selected = mysql_select_db($CFG['UserDBName'], $link_id); + if(!$db_selected) + RevertOneStep( $content['INSTALL_STEP']-1, "Cannot use database " .$CFG['UserDBName'] . "! If the database does not exists, create it or check access permissions!
" . DB_ReturnSimpleErrorMsg()); + + +} +else if ( $content['CONVERT_STEP'] == 3 ) +{ + +} +// --- + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "convert.html"); +$page -> output(); +// --- + +// --- Helper functions + +function RevertOneStep($stepback, $errormsg) +{ + header("Location: convert.php?step=" . $stepback . "&errormsg=" . urlencode($errormsg) ); + exit; +} + +function ForwardOneStep() +{ + global $content; + + header("Location: convert.php?step=" . ($content['CONVERT_STEP']+1) ); + exit; +} +// --- +?> \ No newline at end of file diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 14a9724..4556f5c 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -662,7 +662,7 @@ function CheckUrlOrIP($ip) function DieWithErrorMsg( $szerrmsg ) { global $content; - print(""); + print("phpLogCon :: Critical Error occured"); print("

Critical Error occured


"); print("Errordetails:
" . $szerrmsg); print("
"); diff --git a/src/install.php b/src/install.php index fd9f629..7539322 100644 --- a/src/install.php +++ b/src/install.php @@ -101,7 +101,7 @@ else // --- // --- Set Title -GetAndReplaceLangStr( $content['TITLE'], $content['INSTALL_STEP'] ); +$content['TITLE'] = GetAndReplaceLangStr( $content['TITLE'], $content['INSTALL_STEP'] ); // --- // --- Start Setup Processing diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 80a9775..3309346 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -59,7 +59,6 @@ $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; - // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; $content['LN_MENU_SHOWEVENTS'] = "Show Events"; @@ -176,5 +175,10 @@ $content['LN_LOGIN_SAVEASCOOKIE'] = "Stay logged on"; $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; +// Converter Site +$content['LN_CONVERT_TITLE'] = "Configuration Converter Step %1"; +$content['LN_CONVERT_NOTALLOWED'] = "Login"; + + ?> \ No newline at end of file diff --git a/src/templates/convert.html b/src/templates/convert.html new file mode 100644 index 0000000..4cf24f9 --- /dev/null +++ b/src/templates/convert.html @@ -0,0 +1,343 @@ + + + + {TITLE} + + + + + + + + + + + +
 
+ + + +
+ + + + +
+ +

+ + + + + +
+

Converting phpLogCon configuration settings - Step {CONVERT_STEP}

+
+ + + + + + +
+
+

ERROR: {errormsg}

+
+
+ + +
+ + + + + + +
+ +

Step 1 - Informations

+

This script allows you to import your existing configuration from the config.php file. This includes frontend settings, data sources, custom views and custom searches. Do only perform this conversion if you did install phpLogCon without the UserDB System, and decided to enable it now. +

+ + ANY EXISTING INSTANCE OF A USERDB WILL BE OVERWRITTEN! + +

+ to start the first conversion step!

+

 

+

 

+ +
+ + + + + + + +
+

Step 2 - Create Tables

+

If you reached this step, the database connection has been successfully verified!

+ The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
+ WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database. +

+ Click on to start the creation of the tables + +

 

+

 

+
+ + + + + + + +
+

Step 3 - Check SQL Results

+

Tables have been created. Check the List below for possible Error's

+

    +
  • Successfully executed statements: {sql_sucess}
    +
  • Failed statements: {sql_failed}
    +
+ + You can now proceed to the next step adding the first phpLogCon Admin User! +

 

+ + + + + + + + + + + + + + + + +
At least one statement failed,see error reasons below
Error MessageSQL Statement
{myerrmsg}{mystatement}
+

 

+ + +

 

+
+ + + + + + + +
+

Step 4 - Creating the Main Useraccount

+

You are now about to create the initial phpLogCon User Account.
+ This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center! +

+ + + + + + + + + + + + + + + +
Create User Account
Username
Password
Repeat Password
+ +

 

+

 

+
+ + + + + + + + + +
+
+

Successfully created User '{MAIN_Username}'.

+
+
+ + + + + + +
+

Step 5 - Create the first source for syslog messages

+

 

+ + + + + + + + + + + + + + + +
{LN_CFG_FIRSTSYSLOGSOURCE}
{LN_CFG_NAMEOFTHESOURCE}
{LN_CFG_SOURCETYPE} + +
{LN_CFG_VIEW} + +
+ +
+ + + + + + + + + + +
{LN_CFG_DISKTYPEOPTIONS}
{LN_CFG_LOGLINETYPE} + +
{LN_CFG_SYSLOGFILE}
+
+ +
+ + +
{LN_CFG_DATABASETYPEOPTIONS}
+ +
+ + + + + +
{LN_CFG_DBSTORAGEENGINE} + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{LN_CFG_DBTABLETYPE}
{LN_CFG_DBSERVER}
{LN_CFG_DBNAME}
{LN_CFG_DBTABLENAME}
{LN_CFG_DBUSER}
{LN_CFG_DBPASSWORD}
{LN_CFG_DBROWCOUNTING} + Yes No +
+
+ +

 

+

 

+
+ + + + + + + + + +
+

Step 8 - Done

+

Congratulations! You have successfully installed phpLogCon :D!

+ To finish the Installation, remove the file install.php from the main directory!

+ + Click here to go to your installation. +

 

+

 

+
+ + + + + + + + + + + +
+ Conversion Progress: + + + + +
+
+ + + + + ReCheck + + + Finish! + + +
+ +
+ +
+ \ No newline at end of file From 87bae14900704ec15bb9a86adb3f7b48b7d00c12 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 23 Jul 2008 13:44:54 +0200 Subject: [PATCH 025/142] Fully added the install and convert script into the language system --- src/convert.php | 101 ++++++++++--- src/include/functions_common.php | 4 +- src/include/functions_installhelpers.php | 84 +++++++++++ src/install.php | 72 +++------ src/lang/en/main.php | 71 ++++++++- src/login.php | 4 +- src/templates/convert.html | 183 +++++------------------ src/templates/install.html | 95 ++++++------ 8 files changed, 342 insertions(+), 272 deletions(-) create mode 100644 src/include/functions_installhelpers.php diff --git a/src/convert.php b/src/convert.php index ca3cfad..b8cacfc 100644 --- a/src/convert.php +++ b/src/convert.php @@ -34,12 +34,17 @@ // *** Default includes and procedures *** // define('IN_PHPLOGCON', true); +define('STEPSCRIPTNAME', "convert.php"); // Helper variable for the STEP helper functions $gl_root_path = './'; // Now include necessary include files! include($gl_root_path . 'include/functions_common.php'); include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_installhelpers.php'); +// This site can not require LOGIN +define('IS_NOLOGINPAGE', true); +$content['IS_NOLOGINPAGE'] = true; InitPhpLogCon(); InitSourceConfigs(); InitFrontEndDefaults(); // Only in WebFrontEnd @@ -56,7 +61,7 @@ if ( $content['user_theme'] = "default"; } else - DieWithErrorMsg( 'phpLogCon is not allowed to convert your settings into the user database.

If you want to convert your convert your settings, add the variable following into your config.php:
$CFG[\'UserDBConvertAllowed\'] = true;

Click here to return to pgpLogCon start page.'); + DieWithErrorMsg( $content['LN_CONVERT_ERRORINSTALLED'] ); // --- // --- CONTENT Vars @@ -100,6 +105,7 @@ $content['WidthPlusText'] = "Installer Step " . $content['CONVERT_STEP']; // --- Set Title $content['TITLE'] = GetAndReplaceLangStr( $content['TITLE'], $content['CONVERT_STEP'] ); +$content['LN_CONVERT_TITLETOP'] = GetAndReplaceLangStr( $content['LN_CONVERT_TITLETOP'], $content['CONVERT_STEP'] ); // --- // --- Start Setup Processing @@ -108,18 +114,93 @@ if ( $content['CONVERT_STEP'] == 2 ) // Check the database connect $link_id = mysql_connect( $CFG['UserDBServer'], $CFG['UserDBUser'], $CFG['UserDBPass']); if (!$link_id) - RevertOneStep( $content['INSTALL_STEP']-1, "Connect to " .$CFG['UserDBServer'] . " failed! Check Servername, Port, User and Password!
" . DB_ReturnSimpleErrorMsg() ); + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORCONNECTFAILED'], $CFG['UserDBServer']) . "
" . DB_ReturnSimpleErrorMsg() ); // Try to select the DB! $db_selected = mysql_select_db($CFG['UserDBName'], $link_id); if(!$db_selected) - RevertOneStep( $content['INSTALL_STEP']-1, "Cannot use database " .$CFG['UserDBName'] . "! If the database does not exists, create it or check access permissions!
" . DB_ReturnSimpleErrorMsg()); + RevertOneStep( $content['INSTALL_STEP']-1,GetAndReplaceLangStr( $content['LN_INSTALL_ERRORACCESSDENIED'], $CFG['UserDBName']) . "
" . DB_ReturnSimpleErrorMsg()); } else if ( $content['CONVERT_STEP'] == 3 ) { + // Predefine sql helper vars + $content['sql_sucess'] = 0; + $content['sql_failed'] = 0; + // Import default database if user db is enabled! + if ( $_SESSION['UserDBEnabled'] == 1 ) + { + // Init $totaldbdefs + $totaldbdefs = ""; + + // Read the table GLOBAL definitions + ImportDataFile( $content['BASEPATH'] . "include/db_template.txt" ); + + // Process definitions ^^ + if ( strlen($totaldbdefs) <= 0 ) + { + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINVALIDDBFILE'], $content['BASEPATH'] . "include/db_template.txt"); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; + $content['sql_failed']++; + } + + // Replace stats_ with the custom one ;) + $totaldbdefs = str_replace( "`logcon_", "`" . $_SESSION["UserDBPref"], $totaldbdefs ); + + // Now split by sql command + $mycommands = split( ";\n", $totaldbdefs ); + +// // check for different linefeed +// if ( count($mycommands) <= 1 ) +// $mycommands = split( ";\n", $totaldbdefs ); + + //Still only one? Abort + if ( count($mycommands) <= 1 ) + { + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINSQLCOMMANDS'], $content['BASEPATH'] . "include/db_template.txt"); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; + $content['sql_failed']++; + } + + // Append INSERT Statement for Config Table to set the GameVersion and Database Version ^^! + $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '1', 1)"; + + // --- Now execute all commands + ini_set('error_reporting', E_WARNING); // Enable Warnings! + InitUserDbSettings(); + + // Establish DB Connection + DB_Connect(); + + for($i = 0; $i < count($mycommands); $i++) + { + if ( strlen(trim($mycommands[$i])) > 1 ) + { + $result = DB_Query( $mycommands[$i], false ); + if ($result == FALSE) + { + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = DB_ReturnSimpleErrorMsg(); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = $mycommands[$i]; + + // --- Set CSS Class + if ( $content['sql_failed'] % 2 == 0 ) + $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line1"; + else + $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line2"; + // --- + + $content['sql_failed']++; + } + else + $content['sql_sucess']++; + + // Free result + DB_FreeQuery($result); + } + } + } } // --- @@ -130,19 +211,5 @@ $page -> output(); // --- // --- Helper functions - -function RevertOneStep($stepback, $errormsg) -{ - header("Location: convert.php?step=" . $stepback . "&errormsg=" . urlencode($errormsg) ); - exit; -} - -function ForwardOneStep() -{ - global $content; - - header("Location: convert.php?step=" . ($content['CONVERT_STEP']+1) ); - exit; -} // --- ?> \ No newline at end of file diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 4556f5c..68569ec 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -510,7 +510,7 @@ function InitConfigurationValues() if ( !$content['SESSION_LOGGEDIN'] ) { // User needs to be logged in, redirect to login page - if ( !defined("IS_LOGINPAGE") ) + if ( !defined("IS_NOLOGINPAGE") ) RedirectToUserLogin(); } } @@ -536,7 +536,7 @@ function InitConfigurationValues() } else { - if ( defined('IS_ADMINPAGE') || defined("IS_LOGINPAGE") ) // Language System not initialized yet + if ( defined('IS_ADMINPAGE') || defined("IS_NOLOGINPAGE") ) // Language System not initialized yet DieWithFriendlyErrorMsg( "The phpLogCon user system is currently disabled or not installed." ); } diff --git a/src/include/functions_installhelpers.php b/src/include/functions_installhelpers.php new file mode 100644 index 0000000..8f85288 --- /dev/null +++ b/src/include/functions_installhelpers.php @@ -0,0 +1,84 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * Installer needed functions + * + * -> Functions in this file are only used by the installer + * and converter script. + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- BEGIN Installer Helper Functions --- +function ImportDataFile($szFileName) +{ + global $content, $totaldbdefs; + + // Lets read the table definitions :) + $handle = @fopen($szFileName, "r"); + if ($handle === false) + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr($content['LN_INSTALL_ERRORREADINGDBFILE'], $szFileName) ); + else + { + while (!feof($handle)) + { + $buffer = fgets($handle, 4096); + + $pos = strpos($buffer, "--"); + if ($pos === false) + $totaldbdefs .= $buffer; + else if ( $pos > 2 && strlen( trim($buffer) ) > 1 ) + $totaldbdefs .= $buffer; + } + fclose($handle); + } +} + +function RevertOneStep($stepback, $errormsg) +{ + header("Location: " . STEPSCRIPTNAME . "?step=" . $stepback . "&errormsg=" . urlencode($errormsg) ); + exit; +} + +function ForwardOneStep() +{ + global $content; + + header("Location: " . STEPSCRIPTNAME . "?step=" . ($content['INSTALL_STEP']+1) ); + exit; +} + +// --- + +?> \ No newline at end of file diff --git a/src/install.php b/src/install.php index 7539322..59ec737 100644 --- a/src/install.php +++ b/src/install.php @@ -34,20 +34,21 @@ // *** Default includes and procedures *** // define('IN_PHPLOGCON', true); -define('IN_PHPLOGCON_INSTALL', true); // Extra for INSTALL Script! +define('IN_PHPLOGCON_INSTALL', true); // Extra for INSTALL Script! +define('STEPSCRIPTNAME', "install.php"); // Helper variable for the STEP helper functions $gl_root_path = './'; // Now include necessary include files! include($gl_root_path . 'include/functions_common.php'); include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_installhelpers.php'); // Init Langauge first! IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/main.php' ); InitBasicPhpLogCon(); if ( InitPhpLogConConfigFile(false) ) - DieWithErrorMsg( 'phpLogCon is already configured!

If you want to reconfigure phpLogCon, either delete the current config.php or replace it with an empty file.

Click here to return to pgpLogCon start page.'); -//InitPhpLogCon(); + DieWithErrorMsg( $content['LN_INSTALL_ERRORINSTALLED'] ); // Set some static values define('MAX_STEPS', 8); @@ -59,7 +60,7 @@ $configsamplefile = $content['BASEPATH'] . "include/config.sample.php"; // *** *** // // --- CONTENT Vars -$content['TITLE'] = "phpLogCon :: Installer Step %1"; +$content['TITLE'] = "phpLogCon :: " . $content['LN_INSTALL_TITLE']; // --- // --- Read Vars @@ -102,6 +103,7 @@ else // --- Set Title $content['TITLE'] = GetAndReplaceLangStr( $content['TITLE'], $content['INSTALL_STEP'] ); +$content['LN_INSTALL_TITLETOP'] = GetAndReplaceLangStr( $content['LN_INSTALL_TITLETOP'], $content['BUILDNUMBER'], $content['INSTALL_STEP'] ); // --- // --- Start Setup Processing @@ -179,7 +181,7 @@ if ( $content['INSTALL_STEP'] == 2 ) $content['NEXT_ENABLED'] = "false"; $content['RECHECK_ENABLED'] = "true"; $content['iserror'] = "true"; - $content['errormsg'] = "One file or directory (or more) are not writeable, please check the file permissions (chmod 777)!"; + $content['errormsg'] = $content['LN_INSTALL_FILEORDIRNOTWRITEABLE']; } // Check if sample config file is available @@ -188,7 +190,7 @@ if ( $content['INSTALL_STEP'] == 2 ) $content['NEXT_ENABLED'] = "false"; $content['RECHECK_ENABLED'] = "true"; $content['iserror'] = "true"; - $content['errormsg'] = "The sample configuration file '" . $configsamplefile . "' is missing. You have not fully uploaded phplogcon."; + $content['errormsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_SAMPLECONFIGMISSING'], $configsamplefile); } } @@ -311,12 +313,12 @@ else if ( $content['INSTALL_STEP'] == 4 ) // Now Check database connect $link_id = mysql_connect( $_SESSION['UserDBServer'], $_SESSION['UserDBUser'], $_SESSION['UserDBPass']); if (!$link_id) - RevertOneStep( $content['INSTALL_STEP']-1, "Connect to " .$_SESSION['UserDBServer'] . " failed! Check Servername, Port, User and Password!
" . DB_ReturnSimpleErrorMsg() ); + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORCONNECTFAILED'], $_SESSION['UserDBServer']) . "
" . DB_ReturnSimpleErrorMsg() ); // Try to select the DB! $db_selected = mysql_select_db($_SESSION['UserDBName'], $link_id); if(!$db_selected) - RevertOneStep( $content['INSTALL_STEP']-1, "Cannot use database " .$_SESSION['UserDBName'] . "! If the database does not exists, create it or check access permissions!
" . DB_ReturnSimpleErrorMsg()); + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORACCESSDENIED'], $_SESSION['UserDBName']) . "
" . DB_ReturnSimpleErrorMsg()); } } // --- @@ -373,7 +375,7 @@ else if ( $content['INSTALL_STEP'] == 5 ) // Process definitions ^^ if ( strlen($totaldbdefs) <= 0 ) { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = "Error, invalid Database Defintion File (to short!), file '" . $content['BASEPATH'] . "include/db_template.txt" . "'!
Maybe the file was not correctly uploaded?"; + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINVALIDDBFILE'], $content['BASEPATH'] . "include/db_template.txt"); $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; $content['sql_failed']++; } @@ -391,7 +393,7 @@ else if ( $content['INSTALL_STEP'] == 5 ) //Still only one? Abort if ( count($mycommands) <= 1 ) { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = "Error, invalid Database Defintion File (no statements found!) in '" . $content['BASEPATH'] . "include/db_template.txt" . "'!
Maybe the file was not correctly uploaded, or a strange bug with your system? Contact phpLogCon forums for assistance!"; + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINSQLCOMMANDS'], $content['BASEPATH'] . "include/db_template.txt"); $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; $content['sql_failed']++; } @@ -465,7 +467,7 @@ else if ( $content['INSTALL_STEP'] == 7 ) if ( isset($_POST['username']) ) $_SESSION['MAIN_Username'] = DB_RemoveBadChars($_POST['username']); else - RevertOneStep( $content['INSTALL_STEP']-1, "Username needs to be specified" ); + RevertOneStep( $content['INSTALL_STEP']-1, $content['LN_INSTALL_MISSINGUSERNAME'] ); if ( isset($_POST['password1']) ) $_SESSION['MAIN_Password1'] = DB_RemoveBadChars($_POST['password1']); @@ -481,7 +483,7 @@ else if ( $content['INSTALL_STEP'] == 7 ) strlen($_SESSION['MAIN_Password1']) < 4 || $_SESSION['MAIN_Password1'] != $_SESSION['MAIN_Password2'] ) - RevertOneStep( $content['INSTALL_STEP']-1, "Either the password does not match or is to short!" ); + RevertOneStep( $content['INSTALL_STEP']-1, $content['LN_INSTALL_PASSWORDNOTMATCH'] ); // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! @@ -581,7 +583,7 @@ else if ( $content['INSTALL_STEP'] == 8 ) // Check if access to the configured file is possible if ( !is_file($_SESSION['SourceDiskFile']) ) - RevertOneStep( $content['INSTALL_STEP']-1, "Failed to open the syslog file " .$_SESSION['SourceDiskFile'] . "! Check if the file exists and phplogcon has sufficient rights to it
" ); + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr($content['LN_INSTALL_FAILEDTOOPENSYSLOGFILE'], $_SESSION['SourceDiskFile']) ); } else if ( $_SESSION['SourceType'] == SOURCE_DB || $_SESSION['SourceType'] == SOURCE_PDO ) { @@ -724,7 +726,7 @@ else if ( $content['INSTALL_STEP'] == 8 ) // Create file and write config into it! $handle = fopen( $content['BASEPATH'] . "config.php" , "w"); if ( $handle === false ) - RevertOneStep( $content['INSTALL_STEP']-1, "Coult not create the configuration file " . $content['BASEPATH'] . "config.php" . "! Check File permissions!!!" ); + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr($content['LN_INSTALL_FAILEDCREATECFGFILE'], $content['BASEPATH'] . "config.php") ); fwrite($handle, $filebuffer); fclose($handle); @@ -742,21 +744,6 @@ $page -> output(); // --- // --- Helper functions - -function RevertOneStep($stepback, $errormsg) -{ - header("Location: install.php?step=" . $stepback . "&errormsg=" . urlencode($errormsg) ); - exit; -} - -function ForwardOneStep() -{ - global $content; - - header("Location: install.php?step=" . ($content['INSTALL_STEP']+1) ); - exit; -} - function LoadDataFile($szFileName) { global $content; @@ -765,7 +752,7 @@ function LoadDataFile($szFileName) $buffer = ""; $handle = @fopen($szFileName, "r"); if ($handle === false) - RevertOneStep( $content['INSTALL_STEP']-1, "Error reading the file " . $szFileName . "! Check if the file exists!" ); + RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr($content['LN_INSTALL_FAILEDREADINGFILE'], $szFileName) ); else { while (!feof($handle)) @@ -779,30 +766,6 @@ function LoadDataFile($szFileName) return $buffer; } -function ImportDataFile($szFileName) -{ - global $content, $totaldbdefs; - - // Lets read the table definitions :) - $handle = @fopen($szFileName, "r"); - if ($handle === false) - RevertOneStep( $content['INSTALL_STEP']-1, "Error reading the default database defintion file " . $szFileName . "! Check if the file exists!!!" ); - else - { - while (!feof($handle)) - { - $buffer = fgets($handle, 4096); - - $pos = strpos($buffer, "--"); - if ($pos === false) - $totaldbdefs .= $buffer; - else if ( $pos > 2 && strlen( trim($buffer) ) > 1 ) - $totaldbdefs .= $buffer; - } - fclose($handle); - } -} - function InitUserDbSettings() { global $CFG; @@ -824,6 +787,5 @@ function InitUserDbSettings() define('DB_SOURCES', $CFG['UserDBPref'] . "sources"); define('DB_VIEWS', $CFG['UserDBPref'] . "views"); } - // --- ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 3309346..83f6cfc 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -175,10 +175,79 @@ $content['LN_LOGIN_SAVEASCOOKIE'] = "Stay logged on"; $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; +// Install Site +$content['LN_INSTALL_TITLETOP'] = "Installing phpLogCon Version %1 - Step %1"; +$content['LN_INSTALL_TITLE'] = "Installer Step %1"; +$content['LN_INSTALL_ERRORINSTALLED'] = 'phpLogCon is already configured!

If you want to reconfigure phpLogCon, either delete the current config.php or replace it with an empty file.

Click here to return to pgpLogCon start page.'; +$content['LN_INSTALL_FILEORDIRNOTWRITEABLE'] = "At least one file or directory (or more) is not writeable, please check the file permissions (chmod 666)!"; +$content['LN_INSTALL_SAMPLECONFIGMISSING'] = "The sample configuration file '%1' is missing. You have not fully uploaded phplogcon."; +$content['LN_INSTALL_ERRORCONNECTFAILED'] = "Database connect to '%1' failed! Please check Servername, Port, User and Password!"; +$content['LN_INSTALL_ERRORACCESSDENIED'] = "Cannot use the database '%1'! If the database does not exists, create it or check user access permissions!"; +$content['LN_INSTALL_ERRORINVALIDDBFILE'] = "Error, invalid Database definition file (to short!), the file name is '%1'! Please check if the file was correctly uploaded."; +$content['LN_INSTALL_ERRORINSQLCOMMANDS'] = "Error, invalid Database definition file (no sql statements found!), the file name is '%1'!
Please check if the file was not correctly uploaded, or contact the phpLogCon forums for assistance!"; +$content['LN_INSTALL_MISSINGUSERNAME'] = "Username needs to be specified"; +$content['LN_INSTALL_PASSWORDNOTMATCH'] = "Either the password does not match or is to short!"; +$content['LN_INSTALL_FAILEDTOOPENSYSLOGFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it
"; +$content['LN_INSTALL_FAILEDCREATECFGFILE'] = "Coult not create the configuration file in '%1'! Please verify the file permissions!"; +$content['LN_INSTALL_FAILEDREADINGFILE'] = "Error reading the file '%1'! Please verify if the file exists!"; +$content['LN_INSTALL_ERRORREADINGDBFILE'] = "Error reading the default database definition file in '%1'! Please verify if the file exists!"; +$content['LN_INSTALL_STEP1'] = "Step 1 - Prerequisites"; +$content['LN_INSTALL_STEP2'] = "Step 2 - Verify File Permissions"; +$content['LN_INSTALL_STEP3'] = "Step 3 - Basic Configuration"; +$content['LN_INSTALL_STEP4'] = "Step 4 - Create Tables"; +$content['LN_INSTALL_STEP5'] = "Step 5 - Check SQL Results"; +$content['LN_INSTALL_STEP6'] = "Step 6 - Creating the Main Useraccount"; +$content['LN_INSTALL_STEP7'] = "Step 7 - Create the first source for syslog messages"; +$content['LN_INSTALL_STEP8'] = "Step 8 - Done"; +$content['LN_INSTALL_STEP1_TEXT'] = 'Before you start installing phpLogCon, the Installer setup has to check a few things first.
You may have to correct some file permissions first.

Click on to start the Test!'; +$content['LN_INSTALL_STEP2_TEXT'] = "The following file permissions have been checked. Verify the results below!
You may use the configure.sh script from the contrib folder to set the permissions for you."; +$content['LN_INSTALL_STEP3_TEXT'] = "In this step, you configure the basic configurations for phpLogCon."; +$content['LN_INSTALL_STEP4_TEXT'] = 'If you reached this step, the database connection has been successfully verified!

The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database.

Click on to start the creation of the tables'; +$content['LN_INSTALL_STEP5_TEXT'] = "Tables have been created. Check the List below for possible Error's"; +$content['LN_INSTALL_STEP6_TEXT'] = "You are now about to create the initial phpLogCon User Account.
This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center!"; +$content['LN_INSTALL_STEP8_TEXT'] = 'Congratulations! You have successfully installed phpLogCon :)!

Click here to go to your installation.'; +$content['LN_INSTALL_PROGRESS'] = "Install Progress: "; +$content['LN_INSTALL_FRONTEND'] = "Frontend Options"; +$content['LN_INSTALL_NUMOFSYSLOGS'] = "Number of syslog messages per page"; +$content['LN_INSTALL_MSGCHARLIMIT'] = "Message character limit for the main view"; +$content['LN_INSTALL_SHOWDETAILPOP'] = "Show message details popup"; +$content['LN_INSTALL_AUTORESOLVIP'] = "Automatically resolved IP Addresses (inline)"; +$content['LN_INSTALL_USERDBOPTIONS'] = "User Database Options"; +$content['LN_INSTALL_ENABLEUSERDB'] = "Enable User Database"; +$content['LN_INSTALL_SUCCESSSTATEMENTS'] = "Successfully executed statements:"; +$content['LN_INSTALL_FAILEDSTATEMENTS'] = "Failed statements:"; +$content['LN_INSTALL_STEP5_TEXT_NEXT'] = "You can now proceed to the next step adding the first phpLogCon Admin User!"; +$content['LN_INSTALL_STEP5_TEXT_FAILED'] = "At least one statement failed,see error reasons below"; +$content['LN_INSTALL_ERRORMSG'] = "Error Message"; +$content['LN_INSTALL_SQLSTATEMENT'] = "SQL Statement"; +$content['LN_INSTALL_CREATEUSER'] = "Create User Account"; +$content['LN_INSTALL_PASSWORD'] = "Password"; +$content['LN_INSTALL_PASSWORDREPEAT'] = "Repeat Password"; +$content['LN_INSTALL_SUCCESSCREATED'] = "Successfully created User"; +$content['LN_INSTALL_RECHECK'] = "ReCheck"; +$content['LN_INSTALL_FINISH'] = "Finish!"; +$content['LN_INSTALL_'] = ""; + // Converter Site $content['LN_CONVERT_TITLE'] = "Configuration Converter Step %1"; $content['LN_CONVERT_NOTALLOWED'] = "Login"; - +$content['LN_CONVERT_ERRORINSTALLED'] = 'phpLogCon is not allowed to convert your settings into the user database.

If you want to convert your convert your settings, add the variable following into your config.php:
$CFG[\'UserDBConvertAllowed\'] = true;

Click here to return to pgpLogCon start page.'; +$content['LN_CONVERT_STEP1'] = "Step 1 - Informations"; +$content['LN_CONVERT_STEP2'] = "Step 2 - Create Tables"; +$content['LN_CONVERT_STEP3'] = "Step 3 - Check SQL Results"; +$content['LN_CONVERT_STEP4'] = "Step 4 - Creating the Main Useraccount"; +$content['LN_CONVERT_STEP5'] = "Step 5 - Import Settings into UserDB"; +$content['LN_CONVERT_TITLETOP'] = "Converting phpLogCon configuration settings - Step "; +$content['LN_CONVERT_STEP1_TEXT'] = 'This script allows you to import your existing configuration from the config.php file. This includes frontend settings, data sources, custom views and custom searches. Do only perform this conversion if you did install phpLogCon without the UserDB System, and decided to enable it now.

ANY EXISTING INSTANCE OF A USERDB WILL BE OVERWRITTEN!

to start the first conversion step!'; +$content['LN_CONVERT_STEP2_TEXT'] = 'The database connection has been successfully verified!

The next step will be to create the necessary database tables for the phpLogCon User System. This might take a while!
WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN!
Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database.

Click on to start the creation of the tables'; +$content['LN_CONVERT_STEP5_TEXT'] = ' to start the last step of the conversion. In this step, your existing configuration from the config.php will be imported into the database.'; +$content['LN_CONVERT_STEP6'] = "Step 8 - Done"; +$content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; +$content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; +$content['LN_CONVERT_'] = ""; +$content['LN_CONVERT_'] = ""; +$content['LN_CONVERT_'] = ""; +$content['LN_CONVERT_'] = ""; ?> \ No newline at end of file diff --git a/src/login.php b/src/login.php index a9e4277..81b811b 100644 --- a/src/login.php +++ b/src/login.php @@ -41,8 +41,8 @@ include($gl_root_path . 'include/functions_frontendhelpers.php'); //include($gl_root_path . 'include/functions_filters.php'); // To avoid infinite redirects! -define('IS_LOGINPAGE', true); -$content['IS_LOGINPAGE'] = true; +define('IS_NOLOGINPAGE', true); +$content['IS_NOLOGINPAGE'] = true; InitPhpLogCon(); // --- // diff --git a/src/templates/convert.html b/src/templates/convert.html index 4cf24f9..2a65068 100644 --- a/src/templates/convert.html +++ b/src/templates/convert.html @@ -27,7 +27,7 @@
-

Converting phpLogCon configuration settings - Step {CONVERT_STEP}

+

{LN_CONVERT_TITLETOP}

@@ -51,14 +51,10 @@
-

Step 1 - Informations

-

This script allows you to import your existing configuration from the config.php file. This includes frontend settings, data sources, custom views and custom searches. Do only perform this conversion if you did install phpLogCon without the UserDB System, and decided to enable it now. -

- - ANY EXISTING INSTANCE OF A USERDB WILL BE OVERWRITTEN! - -

- to start the first conversion step!

+

{LN_CONVERT_STEP1}

+

+ {LN_CONVERT_STEP1_TEXT} +

 

 

@@ -71,13 +67,9 @@ @@ -89,24 +81,26 @@
-

Step 2 - Create Tables

-

If you reached this step, the database connection has been successfully verified!

- The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
- WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database. -

- Click on to start the creation of the tables - +

{LN_CONVERT_STEP2}

+

+ {LN_CONVERT_STEP2_TEXT}

 

 

+ + + + + + + From f43629271f490363ac6c1faee5c288915d20a4f6 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 15:23:15 +0200 Subject: [PATCH 099/142] Added support to filter for processid, eventcategory and eventuser Also fixed handling of excluded and included filters! --- src/classes/logstream.class.php | 110 +++++++++++++++++++++++----- src/classes/logstreamdb.class.php | 26 ++++++- src/classes/logstreampdo.class.php | 26 ++++++- src/include/constants_filters.php | 1 + src/include/constants_logstream.php | 11 ++- src/index.php | 45 ++++++++++++ 6 files changed, 185 insertions(+), 34 deletions(-) diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index a27df92..c2e7b55 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -288,8 +288,17 @@ abstract class LogStream { // Check for multiple values! if ( strpos($tmpArray[FILTER_TMP_VALUE], ",") ) - $tmpValues = explode(",", $tmpArray[FILTER_TMP_VALUE]); - + { + // Split by comma and fill tmp Value array + $tmpValueArray = explode(",", $tmpArray[FILTER_TMP_VALUE]); + foreach($tmpValueArray as $myValueEntry) + { + // Append to temp array + $tmpValues[] = array( FILTER_TMP_MODE => $this->SetFilterIncludeMode($myValueEntry), FILTER_TMP_VALUE => $myValueEntry ); + } + } + + // Handle filter based switch( $tmpArray[FILTER_TMP_KEY] ) { case "facility": @@ -300,16 +309,19 @@ abstract class LogStream { { foreach( $tmpValues as $mykey => $szValue ) { - if ( !is_numeric($szValue) ) + if ( !is_numeric($szValue[FILTER_TMP_VALUE]) ) { - $tmpFacilityCode = $this->ConvertFacilityString($szValue); + $tmpFacilityCode = $this->ConvertFacilityString($szValue[FILTER_TMP_VALUE]); if ( $tmpFacilityCode != -1 ) - $tmpValues[$mykey] = $tmpFacilityCode; + $tmpValues[$mykey][FILTER_TMP_VALUE] = $tmpFacilityCode; } } } else { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) { $tmpFacilityCode = $this->ConvertFacilityString($tmpArray[FILTER_TMP_VALUE]); @@ -327,16 +339,19 @@ abstract class LogStream { { foreach( $tmpValues as $mykey => $szValue ) { - if ( !is_numeric($szValue) ) + if ( !is_numeric($szValue[FILTER_TMP_VALUE]) ) { - $tmpFacilityCode = $this->ConvertSeverityString($szValue); + $tmpFacilityCode = $this->ConvertSeverityString($szValue[FILTER_TMP_VALUE]); if ( $tmpFacilityCode != -1 ) - $tmpValues[$mykey] = $tmpFacilityCode; + $tmpValues[$mykey][FILTER_TMP_VALUE] = $tmpFacilityCode; } } } else { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) { $tmpFacilityCode = $this->ConvertSeverityString($tmpArray[FILTER_TMP_VALUE]); @@ -354,16 +369,25 @@ abstract class LogStream { { foreach( $tmpValues as $mykey => $szValue ) { - if ( !is_numeric($szValue) ) + if ( !is_numeric($szValue[FILTER_TMP_VALUE]) ) { - $tmpMsgTypeCode = $this->ConvertMessageTypeString($szValue); + $tmpMsgTypeCode = $this->ConvertMessageTypeString($szValue[FILTER_TMP_VALUE]); if ( $tmpMsgTypeCode != -1 ) - $tmpValues[$mykey] = $tmpMsgTypeCode; + $tmpValues[$mykey][FILTER_TMP_VALUE] = $tmpMsgTypeCode; } } + + foreach( $tmpValues as $mykey => $szValue ) + { + // First set Filter Mode + $tmpValues[$mykey][FILTER_TMP_MODE] = $this->SetFilterIncludeMode($szValue); + } } else { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) { $tmpMsgTypeCode = $this->ConvertMessageTypeString($tmpArray[FILTER_TMP_VALUE]); @@ -381,12 +405,17 @@ abstract class LogStream { { foreach( $tmpValues as $mykey => $szValue ) { - if ( is_numeric($szValue) ) - $tmpValues[$mykey] = $szValue; + if ( is_numeric($szValue[FILTER_TMP_VALUE]) ) + $tmpValues[$mykey][FILTER_TMP_VALUE] = $szValue[FILTER_TMP_VALUE]; + else + $tmpValues[$mykey][FILTER_TMP_VALUE] = ""; } } else { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) $tmpArray[FILTER_TMP_VALUE] = ""; } @@ -401,12 +430,42 @@ abstract class LogStream { { foreach( $tmpValues as $mykey => $szValue ) { - if ( is_numeric($szValue) ) - $tmpValues[$mykey] = $szValue; + if ( is_numeric($szValue[FILTER_TMP_VALUE]) ) + $tmpValues[$mykey][FILTER_TMP_VALUE] = $szValue[FILTER_TMP_VALUE]; + else + $tmpValues[$mykey][FILTER_TMP_VALUE] = ""; } } else { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) + $tmpArray[FILTER_TMP_VALUE] = ""; + + } + // --- + break; + case "eventcategory": + $tmpKeyName = SYSLOG_EVENT_CATEGORY; + $tmpFilterType = FILTER_TYPE_NUMBER; + // --- Extra numeric Check + if ( isset($tmpValues) ) + { + foreach( $tmpValues as $mykey => $szValue ) + { + if ( is_numeric($szValue[FILTER_TMP_VALUE]) ) + $tmpValues[$mykey][FILTER_TMP_VALUE] = $szValue[FILTER_TMP_VALUE]; + else + $tmpValues[$mykey][FILTER_TMP_VALUE] = ""; + } + } + else + { + // First set Filter Mode + $tmpArray[FILTER_TMP_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) $tmpArray[FILTER_TMP_VALUE] = ""; } @@ -420,6 +479,10 @@ abstract class LogStream { $tmpKeyName = SYSLOG_EVENT_SOURCE; $tmpFilterType = FILTER_TYPE_STRING; break; + case "eventuser": + $tmpKeyName = SYSLOG_EVENT_USER; + $tmpFilterType = FILTER_TYPE_STRING; + break; /* END Eventlog based fields */ case "syslogtag": $tmpKeyName = SYSLOG_SYSLOGTAG; @@ -464,10 +527,11 @@ abstract class LogStream { } else if ( isset($tmpValues) ) { - foreach( $tmpValues as $szValue ) +//print_r( $tmpValues ); + foreach( $tmpValues as $szValue ) { // Continue if empty! - if ( strlen(trim($szValue)) == 0 ) + if ( strlen($szValue[FILTER_TMP_VALUE]) == 0 ) continue; if ( isset($this->_filters[$tmpKeyName][$iNum][FILTER_VALUE]) ) @@ -478,16 +542,22 @@ abstract class LogStream { } // Set Filter Mode - $this->_filters[$tmpKeyName][$iNum][FILTER_MODE] = $this->SetFilterIncludeMode($szValue); + if ( isset($szValue[FILTER_TMP_MODE]) ) + $this->_filters[$tmpKeyName][$iNum][FILTER_MODE] = $szValue[FILTER_TMP_MODE]; + else + $this->_filters[$tmpKeyName][$iNum][FILTER_MODE] = $this->SetFilterIncludeMode($szValue[FILTER_TMP_VALUE]); // Set Value - $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] = $szValue; + $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] = $szValue[FILTER_TMP_VALUE]; } } else { // Set Filter Mode - $this->_filters[$tmpKeyName][$iNum][FILTER_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); + if ( isset($tmpArray[FILTER_TMP_MODE]) ) + $this->_filters[$tmpKeyName][$iNum][FILTER_MODE] = $tmpArray[FILTER_TMP_MODE]; + else + $this->_filters[$tmpKeyName][$iNum][FILTER_MODE] = $this->SetFilterIncludeMode($tmpArray[FILTER_TMP_VALUE]); // Set Filter value! $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] = $tmpArray[FILTER_TMP_VALUE]; diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 495b9f4..7ca0622 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -632,13 +632,31 @@ class LogStreamDB extends LogStream { } break; case FILTER_TYPE_NUMBER: - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + // --- Check if user wants to include or exclude! + if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) + { + // Add to filterset + $szArrayKey = $propertyname . "-NOT"; + if ( isset($tmpfilters[$szArrayKey]) ) + $tmpfilters[$szArrayKey][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + else + { + $tmpfilters[$szArrayKey][FILTER_TYPE] = FILTER_TYPE_NUMBER; + $tmpfilters[$szArrayKey][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " NOT IN (" . $myfilter[FILTER_VALUE]; + } + } else { - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + // Add to filterset + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + else + { + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + } } + // --- break; case FILTER_TYPE_DATE: if ( isset($tmpfilters[$propertyname]) ) diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index a0379ef..d5d3736 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -659,13 +659,31 @@ class LogStreamPDO extends LogStream { } break; case FILTER_TYPE_NUMBER: - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + // --- Check if user wants to include or exclude! + if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) + { + // Add to filterset + $szArrayKey = $propertyname . "-NOT"; + if ( isset($tmpfilters[$szArrayKey]) ) + $tmpfilters[$szArrayKey][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + else + { + $tmpfilters[$szArrayKey][FILTER_TYPE] = FILTER_TYPE_NUMBER; + $tmpfilters[$szArrayKey][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " NOT IN (" . $myfilter[FILTER_VALUE]; + } + } else { - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + // Add to filterset + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + else + { + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + } } + // --- break; case FILTER_TYPE_DATE: if ( isset($tmpfilters[$propertyname]) ) diff --git a/src/include/constants_filters.php b/src/include/constants_filters.php index 441bf6a..3b74cd8 100644 --- a/src/include/constants_filters.php +++ b/src/include/constants_filters.php @@ -58,6 +58,7 @@ define('DATE_LASTX_31DAYS', 5); // Helper constants needed for parsing filters define('FILTER_TMP_KEY', 0); define('FILTER_TMP_VALUE', 1); +define('FILTER_TMP_MODE', 2); define('FILTER_DATEMODE', 'datemode'); define('FILTER_TYPE', 'filtertype'); define('FILTER_DATEMODENAME', 'datemodename'); diff --git a/src/include/constants_logstream.php b/src/include/constants_logstream.php index 01c8810..c7fce2d 100644 --- a/src/include/constants_logstream.php +++ b/src/include/constants_logstream.php @@ -143,35 +143,35 @@ $fields[SYSLOG_EVENT_ID]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_EVENT_ID]['Sortable'] = true; $fields[SYSLOG_EVENT_ID]['DefaultWidth'] = "65"; $fields[SYSLOG_EVENT_ID]['FieldAlign'] = "center"; -$fields[SYSLOG_EVENT_ID]['SearchField'] = ""; +$fields[SYSLOG_EVENT_ID]['SearchField'] = "eventid"; $fields[SYSLOG_EVENT_LOGTYPE]['FieldID'] = SYSLOG_EVENT_LOGTYPE; $fields[SYSLOG_EVENT_LOGTYPE]['FieldCaptionID'] = 'LN_FIELDS_EVENTLOGTYPE'; $fields[SYSLOG_EVENT_LOGTYPE]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_EVENT_LOGTYPE]['Sortable'] = true; $fields[SYSLOG_EVENT_LOGTYPE]['DefaultWidth'] = "100"; $fields[SYSLOG_EVENT_LOGTYPE]['FieldAlign'] = "left"; -$fields[SYSLOG_EVENT_LOGTYPE]['SearchField'] = ""; +$fields[SYSLOG_EVENT_LOGTYPE]['SearchField'] = "eventlogtype"; $fields[SYSLOG_EVENT_SOURCE]['FieldID'] = SYSLOG_EVENT_SOURCE; $fields[SYSLOG_EVENT_SOURCE]['FieldCaptionID'] = 'LN_FIELDS_EVENTSOURCE'; $fields[SYSLOG_EVENT_SOURCE]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_EVENT_SOURCE]['Sortable'] = true; $fields[SYSLOG_EVENT_SOURCE]['DefaultWidth'] = "100"; $fields[SYSLOG_EVENT_SOURCE]['FieldAlign'] = "left"; -$fields[SYSLOG_EVENT_SOURCE]['SearchField'] = ""; +$fields[SYSLOG_EVENT_SOURCE]['SearchField'] = "eventlogsource"; $fields[SYSLOG_EVENT_CATEGORY]['FieldID'] = SYSLOG_EVENT_CATEGORY; $fields[SYSLOG_EVENT_CATEGORY]['FieldCaptionID'] = 'LN_FIELDS_EVENTCATEGORY'; $fields[SYSLOG_EVENT_CATEGORY]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_EVENT_CATEGORY]['Sortable'] = true; $fields[SYSLOG_EVENT_CATEGORY]['DefaultWidth'] = "50"; $fields[SYSLOG_EVENT_CATEGORY]['FieldAlign'] = "center"; -$fields[SYSLOG_EVENT_CATEGORY]['SearchField'] = ""; +$fields[SYSLOG_EVENT_CATEGORY]['SearchField'] = "eventcategory"; $fields[SYSLOG_EVENT_USER]['FieldID'] = SYSLOG_EVENT_USER; $fields[SYSLOG_EVENT_USER]['FieldCaptionID'] = 'LN_FIELDS_EVENTUSER'; $fields[SYSLOG_EVENT_USER]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_EVENT_USER]['Sortable'] = true; $fields[SYSLOG_EVENT_USER]['DefaultWidth'] = "85"; $fields[SYSLOG_EVENT_USER]['FieldAlign'] = "left"; -$fields[SYSLOG_EVENT_USER]['SearchField'] = ""; +$fields[SYSLOG_EVENT_USER]['SearchField'] = "eventuser"; // Message is the last element, this order is important for the Detail page for now! $fields[SYSLOG_MESSAGE]['FieldID'] = SYSLOG_MESSAGE; @@ -180,7 +180,6 @@ $fields[SYSLOG_MESSAGE]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_MESSAGE]['Sortable'] = false; $fields[SYSLOG_MESSAGE]['DefaultWidth'] = "100%"; $fields[SYSLOG_MESSAGE]['FieldAlign'] = "left"; - // --- // --- Define default Database field mappings! diff --git a/src/index.php b/src/index.php index da66577..3f6975c 100644 --- a/src/index.php +++ b/src/index.php @@ -525,6 +525,29 @@ if ( isset($content['Sources'][$currentSourceID]) ) 'IconSource' => $content['MENU_NETWORK'] ); } + else if ( $mycolkey == SYSLOG_EVENT_CATEGORY ) + { + // Set OnClick Menu for SYSLOG_EVENT_ID + $content['syslogmessages'][$counter]['values'][$mycolkey]['hasbuttons'] = true; + + // Menu Option to append filter + if ( strlen($content['searchstr']) > 0 ) + { + $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventid%3A' . $logArray[$mycolkey] . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), + 'IconSource' => $content['MENU_BULLET_GREEN'] + ); + } + + // More Menu entries + $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( + 'ButtonUrl' => '?filter=eventcategory%3A' . $logArray[$mycolkey] . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), + 'IconSource' => $content['MENU_BULLET_BLUE'] + ); + } + } else if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_STRING ) { @@ -722,6 +745,28 @@ if ( isset($content['Sources'][$currentSourceID]) ) 'IconSource' => $content['MENU_NETWORK'] ); } + else if ( $mycolkey == SYSLOG_EVENT_USER ) + { + // Set OnClick Menu for SYSLOG_EVENT_USER + $content['syslogmessages'][$counter]['values'][$mycolkey]['hasbuttons'] = true; + + // Menu Option to append filter + if ( strlen($content['searchstr']) > 0 ) + { + $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventuser%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), + 'IconSource' => $content['MENU_BULLET_GREEN'] + ); + } + + // More Menu entries + $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( + 'ButtonUrl' => '?filter=eventuser%3A%3D' . urlencode($szFilterEncodedStr) . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), + 'IconSource' => $content['MENU_BULLET_BLUE'] + ); + } } } } From aa636e59edefc99cce36be0712e810046e729505 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 15:45:43 +0200 Subject: [PATCH 100/142] Fixed some filter issues when creating the where clause --- src/classes/logstreamdb.class.php | 8 ++++---- src/classes/logstreampdo.class.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 7ca0622..e326a86 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -624,11 +624,11 @@ class LogStreamDB extends LogStream { // Now Create LIKE Filters if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . DB_RemoveBadChars($myfilter[FILTER_VALUE]) . $szSearchEnd; else { $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_STRING; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . DB_RemoveBadChars($myfilter[FILTER_VALUE]) . $szSearchEnd; } break; case FILTER_TYPE_NUMBER: @@ -642,7 +642,7 @@ class LogStreamDB extends LogStream { else { $tmpfilters[$szArrayKey][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$szArrayKey][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " NOT IN (" . $myfilter[FILTER_VALUE]; + $tmpfilters[$szArrayKey][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " NOT IN (" . DB_RemoveBadChars($myfilter[FILTER_VALUE]); } } else @@ -653,7 +653,7 @@ class LogStreamDB extends LogStream { else { $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . DB_RemoveBadChars($myfilter[FILTER_VALUE]); } } // --- diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index d5d3736..14eb412 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -651,11 +651,11 @@ class LogStreamPDO extends LogStream { // Not create LIKE Filters if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . DB_RemoveBadChars($myfilter[FILTER_VALUE]) . $szSearchEnd; else { $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_STRING; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . DB_RemoveBadChars($myfilter[FILTER_VALUE]) . $szSearchEnd; } break; case FILTER_TYPE_NUMBER: @@ -669,7 +669,7 @@ class LogStreamPDO extends LogStream { else { $tmpfilters[$szArrayKey][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$szArrayKey][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " NOT IN (" . $myfilter[FILTER_VALUE]; + $tmpfilters[$szArrayKey][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " NOT IN (" . DB_RemoveBadChars($myfilter[FILTER_VALUE]); } } else @@ -680,7 +680,7 @@ class LogStreamPDO extends LogStream { else { $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . DB_RemoveBadChars($myfilter[FILTER_VALUE]); } } // --- From 9603249f46ceca384cf277e00eac3685a5fe6cdc Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 16:31:39 +0200 Subject: [PATCH 101/142] Minor enhancments to the chart generation --- src/chartgenerator.php | 15 ++++++++------- src/include/functions_common.php | 9 ++++++++- src/index.php | 2 +- src/statistics.php | 5 ++++- src/templates/statistics.html | 5 ++++- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 4f764d2..e3821ce 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -32,16 +32,17 @@ */ // *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); +if ( !defined('IN_PHPLOGCON') ) + define('IN_PHPLOGCON', true); $gl_root_path = './'; // Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); +include_once($gl_root_path . 'include/functions_common.php'); +include_once($gl_root_path . 'include/functions_frontendhelpers.php'); +include_once($gl_root_path . 'include/functions_filters.php'); // Include LogStream facility -include($gl_root_path . 'classes/logstream.class.php'); +include_once($gl_root_path . 'classes/logstream.class.php'); InitPhpLogCon(); InitSourceConfigs(); @@ -465,8 +466,8 @@ if ( $content['error_occured'] ) // --- // --- Output the image -//$graph->Stroke(); -$graph->StrokeCSIM(); +//$graph->Stroke(); +$graph->StrokeCSIM( basename(__FILE__), '', 0); // --- ?> \ No newline at end of file diff --git a/src/include/functions_common.php b/src/include/functions_common.php index f48c585..1a789d1 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -108,7 +108,7 @@ function InitUserSystemPhpLogCon() if ( GetConfigSetting("UserDBEnabled", false) ) { // Include User Functions - include($gl_root_path . 'include/functions_users.php'); + include_once($gl_root_path . 'include/functions_users.php'); } } @@ -141,6 +141,10 @@ function InitPhpLogCon() // Needed to make global global $gl_root_path, $content; + // Abort if already defined + if ( defined('PHPLOGCON_INITIALIZED') ) + return; + // Init Basics which do not need a database InitBasicPhpLogCon(); @@ -181,6 +185,9 @@ function InitPhpLogCon() // --- Enable PHP Debug Mode InitPhpDebugMode(); // --- + + // Finally defined PHPLOGCON_INITIALIZED! + define( 'PHPLOGCON_INITIALIZED', TRUE ); } function CreateLogLineTypesList( $selectedType ) diff --git a/src/index.php b/src/index.php index 3f6975c..73c9d1b 100644 --- a/src/index.php +++ b/src/index.php @@ -534,7 +534,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) if ( strlen($content['searchstr']) > 0 ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( - 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventid%3A' . $logArray[$mycolkey] . '&search=Search' . $content['additional_url_sourceonly'], + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+eventcategory%3A' . $logArray[$mycolkey] . '&search=Search' . $content['additional_url_sourceonly'], 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), 'IconSource' => $content['MENU_BULLET_GREEN'] ); diff --git a/src/statistics.php b/src/statistics.php index 9794d13..9fe7dd7 100644 --- a/src/statistics.php +++ b/src/statistics.php @@ -61,11 +61,14 @@ if ( isset($content['Charts']) ) // PreProcess Charts Array for display! $i = 0; // Help counter! - foreach ($content['Charts'] as &$myChart ) + foreach ($content['Charts'] as $myChartID => &$myChart ) { // Only process if chart is enabled if ( isset($myChart['chart_enabled']) && $myChart['chart_enabled'] == 1 ) { + // Set Chart ID + $myChart['CHART_ID'] = $myChartID; + // --- Set display name for chart type switch($myChart['chart_type']) { diff --git a/src/templates/statistics.html b/src/templates/statistics.html index dfaebf5..ca1230c 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -50,7 +50,10 @@
-

Step 3 - Check SQL Results

-

Tables have been created. Check the List below for possible Error's

+

{LN_CONVERT_STEP3}

+

+ {LN_INSTALL_STEP5_TEXT} +

    -
  • Successfully executed statements: {sql_sucess}
    -
  • Failed statements: {sql_failed}
    +
  • {LN_INSTALL_SUCCESSSTATEMENTS} {sql_sucess}
    +
  • {LN_INSTALL_FAILEDSTATEMENTS} {sql_failed}
- You can now proceed to the next step adding the first phpLogCon Admin User! + {LN_INSTALL_STEP5_TEXT_NEXT}

 

- + - - + + @@ -128,23 +122,24 @@
At least one statement failed,see error reasons below{LN_INSTALL_STEP5_TEXT_FAILED}
Error MessageSQL Statement{LN_INSTALL_ERRORMSG}{LN_INSTALL_SQLSTATEMENT}
@@ -173,113 +168,13 @@
-

Step 4 - Creating the Main Useraccount

-

You are now about to create the initial phpLogCon User Account.
- This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center! +

{LN_CONVERT_STEP4}

+

+ {LN_INSTALL_STEP6_TEXT} +



- + - + - + - +
Create User Account
{LN_INSTALL_CREATEUSER}
Username{LN_LOGIN_USERNAME}
Password{LN_INSTALL_PASSWORD}
Repeat Password{LN_INSTALL_PASSWORDREPEAT}
@@ -163,7 +158,7 @@

-

Successfully created User '{MAIN_Username}'.

+

{LN_INSTALL_SUCCESSCREATED} '{MAIN_Username}'.


-

Step 5 - Create the first source for syslog messages

-

 

- - - - - - - - - - - - - - - -
{LN_CFG_FIRSTSYSLOGSOURCE}
{LN_CFG_NAMEOFTHESOURCE}
{LN_CFG_SOURCETYPE} - -
{LN_CFG_VIEW} - -
- -
- - - - - - - - - - -
{LN_CFG_DISKTYPEOPTIONS}
{LN_CFG_LOGLINETYPE} - -
{LN_CFG_SYSLOGFILE}
-
- -
- - -
{LN_CFG_DATABASETYPEOPTIONS}
- -
- - - - - -
{LN_CFG_DBSTORAGEENGINE} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{LN_CFG_DBTABLETYPE}
{LN_CFG_DBSERVER}
{LN_CFG_DBNAME}
{LN_CFG_DBTABLENAME}
{LN_CFG_DBUSER}
{LN_CFG_DBPASSWORD}
{LN_CFG_DBROWCOUNTING} - Yes No -
-
- +

{LN_CONVERT_STEP5}

+

+ {LN_CONVERT_STEP5_TEXT} +

 

 

+
@@ -294,11 +189,9 @@ @@ -311,7 +204,7 @@ - + From 243be31e82ca497e852681109a48e2a1fcf65030 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 17:35:26 +0200 Subject: [PATCH 097/142] Added IMAP MAP support into the new charts. --- src/chartgenerator.php | 16 +++++++++++++--- src/templates/statistics.html | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 772fd9b..339e6f8 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -147,6 +147,9 @@ if ( !$content['error_occured'] ) // echo $myKey . "
"; $YchartData[] = intval($myData); $XchartData[] = strlen($myKey) > 0 ? $myKey : "Unknown"; + $chartImageMapLinks[] = $content['BASEPATH'] . "index.php?filter=" . strtolower ( $fields[$content['chart_field']]['FieldID'] ) . "%3A%3D" . $myKey . "&search=Search"; + $chartImageMapAlts[] = $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . ": " . $myKey; + $chartImageMapTargets[] ="_top"; } if ( $content['chart_type'] == CHART_CAKE ) @@ -197,7 +200,7 @@ if ( !$content['error_occured'] ) // $targ=array("pie3d_csimex1.php?v=1","pie3d_csimex1.php?v=2","pie3d_csimex1.php?v=3", // "pie3d_csimex1.php?v=4","pie3d_csimex1.php?v=5","pie3d_csimex1.php?v=6"); // $alts=array("val=%d","val=%d","val=%d","val=%d","val=%d","val=%d"); -// $p1->SetCSIMTargets($targ,$alts); + $p1->SetCSIMTargets($chartImageMapLinks, $chartImageMapAlts, $chartImageMapTargets); // Set label format if ( $content['showpercent'] == 1 ) @@ -285,6 +288,9 @@ if ( !$content['error_occured'] ) // $bplot->value->SetAlign('left','center'); // $bplot->value->SetColor("black","darkred"); $bplot->value->SetFormat('%d'); + + // Add links + $bplot->SetCSIMTargets($chartImageMapLinks, $chartImageMapAlts, $chartImageMapTargets); // TODO: Make Optional! @@ -372,6 +378,10 @@ if ( !$content['error_occured'] ) // $bplot->value->SetColor("black","darkred"); $bplot->value->SetFormat('%d'); + // Add links + $bplot->SetCSIMTargets($chartImageMapLinks, $chartImageMapAlts, $chartImageMapTargets); + + // TODO: Make Optional! // Create and Add filled line plot $lplot = new LinePlot($YchartData); @@ -451,8 +461,8 @@ if ( $content['error_occured'] ) // --- // --- Output the image -$graph->Stroke(); -//$graph->StrokeCSIM(); +//$graph->Stroke(); +$graph->StrokeCSIM(); // --- ?> \ No newline at end of file diff --git a/src/templates/statistics.html b/src/templates/statistics.html index ec067f5..dfaebf5 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -50,7 +50,7 @@
-

Step 8 - Done

-

Congratulations! You have successfully installed phpLogCon :D!

- To finish the Installation, remove the file install.php from the main directory!

- - Click here to go to your installation. +

{LN_CONVERT_STEP6}

+

+ {LN_CONVERT_STEP6_TEXT}

 

 

Conversion Progress: {LN_CONVERT_PROCESS} @@ -324,10 +217,10 @@ - ReCheck + {LN_INSTALL_RECHECK} - Finish! + {LN_INSTALL_FINISH} + From 1b5739e8db8db805b7a9a619390f6e44f2662143 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 15:53:30 +0200 Subject: [PATCH 093/142] Finished Chart Admin --- src/admin/charts.php | 452 ++++++-------------------- src/include/functions_common.php | 41 +++ src/include/functions_config.php | 23 +- src/lang/de/admin.php | 14 + src/lang/en/admin.php | 16 +- src/lang/en/main.php | 1 + src/lang/pt_BR/admin.php | 14 + src/statistics.php | 2 +- src/templates/admin/admin_charts.html | 181 +++-------- 9 files changed, 249 insertions(+), 495 deletions(-) diff --git a/src/admin/charts.php b/src/admin/charts.php index 5ebe5ab..5846a47 100644 --- a/src/admin/charts.php +++ b/src/admin/charts.php @@ -59,51 +59,28 @@ if ( isset($_GET['op']) ) if ($_GET['op'] == "add") { // Set Mode to add - $content['ISEDITORNEWSOURCE'] = "true"; - $content['SOURCE_FORMACTION'] = "addnewsource"; - $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_ADD']; + $content['ISEDITORNEWCHART'] = "true"; + $content['CHART_FORMACTION'] = "addnewchart"; + $content['CHART_SENDBUTTON'] = $content['LN_CHARTS_ADD']; //PreInit these values - $content['Name'] = ""; - $content['SourceType'] = SOURCE_DISK; - CreateSourceTypesList($content['SourceType']); - $content['MsgParserList'] = ""; - $content['MsgNormalize'] = 0; - $content['CHECKED_ISNORMALIZEMSG'] = ""; + $content['Name'] = "MyChart"; + $content['chart_type'] = CHART_BARS_VERTICAL; + CreateChartTypesList($content['chart_type']); + $content['chart_enabled'] = 1; + $content['CHECKED_ISCHARTENABLED'] = "checked"; + $content['chart_width'] = 400; + $content['maxrecords'] = 5; + $content['showpercent'] = 0; + $content['CHECKED_ISSHOWPERCENT'] = ""; + // Chart Field + $content['chart_field'] = SYSLOG_HOST; + CreateChartFields($content['chart_field']); - // Init View List! - $content['SourceViewID'] = 'SYSLOG'; - $content['VIEWS'] = $content['Views']; - foreach ( $content['VIEWS'] as $myView ) - { - if ( $myView['ID'] == $content['SourceViewID'] ) - $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; - else - $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; - } - - // SOURCE_DISK specific - $content['SourceLogLineType'] = ""; - CreateLogLineTypesList($content['SourceLogLineType']); - $content['SourceDiskFile'] = "/var/log/syslog"; - - // SOURCE_DB specific - $content['SourceDBType'] = DB_MYSQL; - CreateDBTypesList($content['SourceDBType']); - $content['SourceDBName'] = "phplogcon"; - $content['SourceDBTableType'] = "monitorware"; - $content['SourceDBServer'] = "localhost"; - $content['SourceDBTableName'] = "systemevents"; - $content['SourceDBUser'] = "user"; - $content['SourceDBPassword'] = ""; - $content['SourceDBEnableRowCounting'] = "false"; - $content['SourceDBEnableRowCounting_true'] = ""; - $content['SourceDBEnableRowCounting_false'] = "checked"; - - // General stuff + // COMMON Fields $content['userid'] = null; $content['CHECKED_ISUSERONLY'] = ""; - $content['SOURCEID'] = ""; + $content['CHARTID'] = ""; // --- Check if groups are available $content['SUBGROUPS'] = GetGroupsForSelectfield(); @@ -115,70 +92,44 @@ if ( isset($_GET['op']) ) else if ($_GET['op'] == "edit") { // Set Mode to edit - $content['ISEDITORNEWSOURCE'] = "true"; - $content['SOURCE_FORMACTION'] = "editsource"; - $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_EDIT']; + $content['ISEDITORNEWCHART'] = "true"; + $content['CHART_FORMACTION'] = "editchart"; + $content['CHART_SENDBUTTON'] = $content['LN_CHARTS_EDIT']; if ( isset($_GET['id']) ) { //PreInit these values - $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); + $content['CHARTID'] = DB_RemoveBadChars($_GET['id']); // Check if exists - if ( is_numeric($content['SOURCEID']) && isset($content['Sources'][ $content['SOURCEID'] ]) ) + if ( is_numeric($content['CHARTID']) && isset($content['Charts'][ $content['CHARTID'] ]) ) { // Get Source reference - $mysource = $content['Sources'][ $content['SOURCEID'] ]; + $myChart = $content['Charts'][ $content['CHARTID'] ]; // Copy basic properties - $content['Name'] = $mysource['Name']; - $content['SourceType'] = $mysource['SourceType']; - CreateSourceTypesList($content['SourceType']); - $content['MsgParserList'] = $mysource['MsgParserList']; - $content['MsgNormalize'] = $mysource['MsgNormalize']; - if ( $mysource['MsgNormalize'] == 1 ) - $content['CHECKED_ISNORMALIZEMSG'] = "checked"; + $content['Name'] = $myChart['DisplayName']; + $content['chart_type'] = $myChart['chart_type']; + CreateChartTypesList($content['chart_type']); + $content['chart_enabled'] = $myChart['chart_enabled']; + if ( $myChart['chart_enabled'] == 1 ) + $content['CHECKED_ISCHARTENABLED'] = "checked"; else - $content['CHECKED_ISNORMALIZEMSG'] = ""; - - // Init View List! - $content['SourceViewID'] = $mysource['ViewID']; - $content['VIEWS'] = $content['Views']; - foreach ( $content['VIEWS'] as $myView ) - { - if ( $myView['ID'] == $content['SourceViewID'] ) - $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; - else - $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; - } - - // SOURCE_DISK specific - $content['SourceLogLineType'] = $mysource['LogLineType']; - CreateLogLineTypesList($content['SourceLogLineType']); - $content['SourceDiskFile'] = $mysource['DiskFile']; - - // SOURCE_DB specific - $content['SourceDBType'] = $mysource['DBType']; - CreateDBTypesList($content['SourceDBType']); - $content['SourceDBName'] = $mysource['DBName']; - $content['SourceDBTableType'] = $mysource['DBTableType']; - $content['SourceDBServer'] = $mysource['DBServer']; - $content['SourceDBTableName'] = $mysource['DBTableName']; - $content['SourceDBUser'] = $mysource['DBUser']; - $content['SourceDBPassword'] = $mysource['DBPassword']; - $content['SourceDBEnableRowCounting'] = $mysource['DBEnableRowCounting']; - if ( $content['SourceDBEnableRowCounting'] == 1 ) - { - $content['SourceDBEnableRowCounting_true'] = "checked"; - $content['SourceDBEnableRowCounting_false'] = ""; - } + $content['CHECKED_ISCHARTENABLED'] = ""; + $content['chart_width'] = $myChart['chart_width']; + $content['maxrecords'] = $myChart['maxrecords']; + $content['showpercent'] = $myChart['showpercent']; + if ( $myChart['showpercent'] == 1 ) + $content['CHECKED_ISSHOWPERCENT'] = "checked"; else - { - $content['SourceDBEnableRowCounting_true'] = ""; - $content['SourceDBEnableRowCounting_false'] = "checked"; - } + $content['CHECKED_ISSHOWPERCENT'] = ""; - if ( $mysource['userid'] != null ) + // Chart Field + $content['chart_field'] = $myChart['chart_field']; + CreateChartFields($content['chart_field']); + + // COMMON Fields + if ( $myChart['userid'] != null ) $content['CHECKED_ISUSERONLY'] = "checked"; else $content['CHECKED_ISUSERONLY'] = ""; @@ -190,7 +141,7 @@ if ( isset($_GET['op']) ) // Process All Groups for($i = 0; $i < count($content['SUBGROUPS']); $i++) { - if ( $mysource['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysource['groupid'] ) + if ( $myChart['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $myChart['groupid'] ) $content['SUBGROUPS'][$i]['group_selected'] = "selected"; else $content['SUBGROUPS'][$i]['group_selected'] = ""; @@ -205,16 +156,16 @@ if ( isset($_GET['op']) ) } else { - $content['ISEDITORNEWSOURCE'] = false; + $content['ISEDITORNEWCHART'] = false; $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; + $content['ERROR_MSG'] = "!" . $content['LN_CHARTS_ERROR_INVALIDORNOTFOUNDID']; } } else { - $content['ISEDITORNEWSEARCH'] = false; + $content['ISEDITORNEWCHART'] = false; $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + $content['ERROR_MSG'] = $content['LN_CHARTS_ERROR_INVALIDID']; } } else if ($_GET['op'] == "delete") @@ -222,42 +173,42 @@ if ( isset($_GET['op']) ) if ( isset($_GET['id']) ) { //PreInit these values - $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); + $content['CHARTID'] = DB_RemoveBadChars($_GET['id']); // Get UserInfo - $result = DB_Query("SELECT Name FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); + $result = DB_Query("SELECT DisplayName FROM " . DB_CHARTS . " WHERE ID = " . $content['CHARTID'] ); $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['Name']) ) + if ( !isset($myrow['DisplayName']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_IDNOTFOUND'], $content['CHARTID'] ); } // --- Ask for deletion first! if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) { // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SOURCES_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_CHARTS_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); } // --- // do the delete! - $result = DB_Query( "DELETE FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); + $result = DB_Query( "DELETE FROM " . DB_CHARTS . " WHERE ID = " . $content['CHARTID'] ); if ($result == FALSE) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_DELSOURCE'], $content['SOURCEID'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_DELCHART'], $content['CHARTID'] ); } else DB_FreeQuery($result); // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_HASBEENDEL'], $myrow['Name'] ) , "sources.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_HASBEENDEL'], $myrow['Name'] ) , "charts.php" ); } else { $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; + $content['ERROR_MSG'] = $content['LN_CHARTS_ERROR_INVALIDORNOTFOUNDID']; } } } @@ -265,38 +216,15 @@ if ( isset($_GET['op']) ) if ( isset($_POST['op']) ) { // Read parameters first! - if ( isset($_POST['id']) ) { $content['SOURCEID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SOURCEID'] = -1; } + if ( isset($_POST['id']) ) { $content['CHARTID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['CHARTID'] = -1; } if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } - if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } - if ( isset($_POST['MsgParserList']) ) { $content['MsgParserList'] = DB_RemoveBadChars($_POST['MsgParserList']); } - if ( isset($_POST['MsgNormalize']) ) { $content['MsgNormalize'] = intval(DB_RemoveBadChars($_POST['MsgNormalize'])); } else {$content['MsgNormalize'] = 0; } - if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } - - if ( isset($content['SourceType']) ) - { - // Disk Params - if ( $content['SourceType'] == SOURCE_DISK ) - { - if ( isset($_POST['SourceLogLineType']) ) { $content['SourceLogLineType'] = DB_RemoveBadChars($_POST['SourceLogLineType']); } - if ( isset($_POST['SourceDiskFile']) ) { $content['SourceDiskFile'] = DB_RemoveBadChars($_POST['SourceDiskFile']); } - } - // DB Params - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - if ( isset($_POST['SourceDBType']) ) { $content['SourceDBType'] = DB_RemoveBadChars($_POST['SourceDBType']); } - if ( isset($_POST['SourceDBName']) ) { $content['SourceDBName'] = DB_RemoveBadChars($_POST['SourceDBName']); } - if ( isset($_POST['SourceDBTableType']) ) { $content['SourceDBTableType'] = DB_RemoveBadChars($_POST['SourceDBTableType']); } - if ( isset($_POST['SourceDBServer']) ) { $content['SourceDBServer'] = DB_RemoveBadChars($_POST['SourceDBServer']); } - if ( isset($_POST['SourceDBTableName']) ) { $content['SourceDBTableName'] = DB_RemoveBadChars($_POST['SourceDBTableName']); } - if ( isset($_POST['SourceDBUser']) ) { $content['SourceDBUser'] = DB_RemoveBadChars($_POST['SourceDBUser']); } - if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } - if ( isset($_POST['SourceDBEnableRowCounting']) ) { $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceDBEnableRowCounting']); } - // Extra Check for this property - if ( $content['SourceDBEnableRowCounting'] != "true" ) - $content['SourceDBEnableRowCounting'] = "false"; - } - } - + if ( isset($_POST['chart_enabled']) ) { $content['chart_enabled'] = intval(DB_RemoveBadChars($_POST['chart_enabled'])); } else {$content['chart_enabled'] = 0; } + if ( isset($_POST['chart_type']) ) { $content['chart_type'] = intval(DB_RemoveBadChars($_POST['chart_type'])); } + if ( isset($_POST['chart_width']) ) { $content['chart_width'] = intval(DB_RemoveBadChars($_POST['chart_width'])); } else {$content['chart_width'] = 400; } + if ( isset($_POST['chart_field']) ) { $content['chart_field'] = DB_RemoveBadChars($_POST['chart_field']); } + if ( isset($_POST['maxrecords']) ) { $content['maxrecords'] = intval(DB_RemoveBadChars($_POST['maxrecords'])); } + if ( isset($_POST['showpercent']) ) { $content['showpercent'] = intval(DB_RemoveBadChars($_POST['showpercent'])); } else {$content['showpercent'] = 0; } + // User & Group handeled specially if ( isset ($_POST['isuseronly']) ) { @@ -316,250 +244,72 @@ if ( isset($_POST['op']) ) if ( $content['Name'] == "" ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_NAMEOFTHESOURCE'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_MISSINGPARAM'], $content['LN_CHARTS_NAME'] ); } - else if ( !isset($content['SourceType']) ) + else if ( !isset($content['chart_type']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SOURCETYPE'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_MISSINGPARAM'], $content['LN_CHART_TYPE'] ); } - else if ( !isset($content['SourceViewID']) ) + else if ( !isset($content['chart_field']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_VIEW'] ); - } - else - { - // Disk Params - if ( $content['SourceType'] == SOURCE_DISK ) - { - if ( !isset($content['SourceLogLineType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_LOGLINETYPE'] ); - } - else if ( !isset($content['SourceDiskFile']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SYSLOGFILE'] ); - } - // Check if file is accessable! - else - { - // Get plain filename for testing! - $content['SourceDiskFileTesting'] = DB_StripSlahes($content['SourceDiskFile']); - - // Take as it is if rootpath! - if ( - ( ($pos = strpos($content['SourceDiskFileTesting'], "/")) !== FALSE && $pos == 0) || - ( ($pos = strpos($content['SourceDiskFileTesting'], "\\\\")) !== FALSE && $pos == 0) || - ( ($pos = strpos($content['SourceDiskFileTesting'], ":\\")) !== FALSE ) || - ( ($pos = strpos($content['SourceDiskFileTesting'], ":/")) !== FALSE ) - ) - { - // Nothing really todo - true; - } - else // prepend basepath! - $content['SourceDiskFileTesting'] = $gl_root_path . $content['SourceDiskFileTesting']; -/* - if ( !is_file($content['SourceDiskFileTesting']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $szFileName ); - } -*/ - } - } - // DB Params - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - if ( !isset($content['SourceDBType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DATABASETYPEOPTIONS'] ); - } - else if ( !isset($content['SourceDBName']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBNAME'] ); - } - else if ( !isset($content['SourceDBTableType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLETYPE'] ); - } - else if ( !isset($content['SourceDBServer']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBSERVER'] ); - } - else if ( !isset($content['SourceDBTableName']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLENAME'] ); - } - else if ( !isset($content['SourceDBUser']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBUSER'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'], $content['SourceDBType'] ); - } - - // --- Verify the Source and report and error if needed! - - // Include LogStream facility - include($gl_root_path . 'classes/logstream.class.php'); - - // First create a tmp source array - $tmpSource['ID'] = $content['SOURCEID']; - $tmpSource['Name'] = $content['Name']; - $tmpSource['SourceType'] = $content['SourceType']; - $tmpSource['MsgParserList'] = $content['MsgParserList']; - $tmpSource['MsgNormalize'] = $content['MsgNormalize']; - $tmpSource['ViewID'] = $content['SourceViewID']; - if ( $tmpSource['SourceType'] == SOURCE_DISK ) - { - $tmpSource['LogLineType'] = $content['SourceLogLineType']; - $tmpSource['DiskFile'] = $content['SourceDiskFileTesting']; // use SourceDiskFileTesting rather then SourceDiskFile as it is corrected - } - // DB Params - else if ( $tmpSource['SourceType'] == SOURCE_DB || $tmpSource['SourceType'] == SOURCE_PDO ) - { - $tmpSource['DBType'] = $content['SourceDBType']; - $tmpSource['DBName'] = $content['SourceDBName']; - $tmpSource['DBTableType'] = $content['SourceDBTableType']; - $tmpSource['DBServer'] = $content['SourceDBServer']; - $tmpSource['DBTableName'] = $content['SourceDBTableName']; - $tmpSource['DBUser'] = $content['SourceDBUser']; - $tmpSource['DBPassword'] = $content['SourceDBPassword']; - $tmpSource['DBEnableRowCounting'] = $content['SourceDBEnableRowCounting']; - $tmpSource['userid'] = $content['userid']; - $tmpSource['groupid'] = $content['groupid']; - } - - // Init the source - InitSource($tmpSource); - - // Create LogStream Object - $stream = $tmpSource['ObjRef']->LogStreamFactory($tmpSource['ObjRef']); - $res = $stream->Verify(); - if ( $res != SUCCESS ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_WITHINSOURCE'], $tmpSource['Name'], GetErrorMessage($res) ); - - if ( isset($extraErrorDescription) ) - $content['ERROR_MSG'] .= "

" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); - } - // --- + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_MISSINGPARAM'], $content['LN_CHART_FIELD'] ); } // --- Now ADD/EDIT do the processing! if ( !isset($content['ISERROR']) ) { // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewsource" ) + if ( $_POST['op'] == "addnewchart" ) { // Add custom search now! - if ( $content['SourceType'] == SOURCE_DISK ) - { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, LogLineType, DiskFile, userid, groupid) - VALUES ('" . $content['Name'] . "', - " . $content['SourceType'] . ", - '" . $content['MsgParserList'] . "', - " . $content['MsgNormalize'] . ", - '" . $content['SourceViewID'] . "', - '" . $content['SourceLogLineType'] . "', - '" . $content['SourceDiskFile'] . "', - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; - } - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) - VALUES ('" . $content['Name'] . "', - " . $content['SourceType'] . ", - '" . $content['MsgParserList'] . "', - " . $content['MsgNormalize'] . ", - '" . $content['SourceViewID'] . "', - '" . $content['SourceDBTableType'] . "', - " . $content['SourceDBType'] . ", - '" . $content['SourceDBServer'] . "', - '" . $content['SourceDBName'] . "', - '" . $content['SourceDBUser'] . "', - '" . $content['SourceDBPassword'] . "', - '" . $content['SourceDBTableName'] . "', - " . $content['SourceDBEnableRowCounting'] . ", - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; - } + $sqlquery = "INSERT INTO " . DB_CHARTS . " (DisplayName, chart_enabled, chart_type, chart_width, chart_field, maxrecords, showpercent, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['chart_enabled'] . ", + " . $content['chart_type'] . ", + " . $content['chart_width'] . ", + '" . $content['chart_field'] . "', + " . $content['maxrecords'] . ", + " . $content['showpercent'] . ", + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; $result = DB_Query($sqlquery); DB_FreeQuery($result); // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCE_HASBEENADDED'], $content['Name'] ) , "sources.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_CHARTS_HASBEENADDED'], $content['Name'] ) , "charts.php" ); } - else if ( $_POST['op'] == "editsource" ) + else if ( $_POST['op'] == "editchart" ) { - $result = DB_Query("SELECT ID FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID']); + $result = DB_Query("SELECT ID FROM " . DB_CHARTS . " WHERE ID = " . $content['CHARTID']); $myrow = DB_GetSingleRow($result, true); if ( !isset($myrow['ID']) ) { $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_IDNOTFOUND'], $content['CHARTID'] ); } else { - // Edit the Search Entry now! - if ( $content['SourceType'] == SOURCE_DISK ) - { - $sqlquery = "UPDATE " . DB_SOURCES . " SET - Name = '" . $content['Name'] . "', - SourceType = " . $content['SourceType'] . ", - MsgParserList = '" . $content['MsgParserList'] . "', - MsgNormalize = " . $content['MsgNormalize'] . ", - ViewID = '" . $content['SourceViewID'] . "', - LogLineType = '" . $content['SourceLogLineType'] . "', - DiskFile = '" . $content['SourceDiskFile'] . "', - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['SOURCEID']; - } - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - $sqlquery = "UPDATE " . DB_SOURCES . " SET - Name = '" . $content['Name'] . "', - SourceType = " . $content['SourceType'] . ", - MsgParserList = '" . $content['MsgParserList'] . "', - MsgNormalize = " . $content['MsgNormalize'] . ", - ViewID = '" . $content['SourceViewID'] . "', - DBTableType = '" . $content['SourceDBTableType'] . "', - DBType = " . $content['SourceDBType'] . ", - DBServer = '" . $content['SourceDBServer'] . "', - DBName = '" . $content['SourceDBName'] . "', - DBUser = '" . $content['SourceDBUser'] . "', - DBPassword = '" . $content['SourceDBPassword'] . "', - DBTableName = '" . $content['SourceDBTableName'] . "', - DBEnableRowCounting = " . $content['SourceDBEnableRowCounting'] . ", - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['SOURCEID']; - } + $sqlquery = "UPDATE " . DB_CHARTS . " SET + DisplayName = '" . $content['Name'] . "', + chart_enabled = " . $content['chart_enabled'] . ", + chart_type = " . $content['chart_type'] . ", + chart_width = " . $content['chart_width'] . ", + chart_field = '" . $content['chart_field'] . "', + maxrecords = " . $content['maxrecords'] . ", + showpercent = " . $content['showpercent'] . ", + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['CHARTID']; $result = DB_Query($sqlquery); DB_FreeQuery($result); // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_HASBEENEDIT'], $content['Name']) , "sources.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_CHARTS_HASBEENEDIT'], $content['Name']) , "charts.php" ); } } } @@ -636,6 +386,14 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) } // --- + // --- Set enabled or disabled state + if ( $myChart['chart_enabled'] == 1 ) + $myChart['ChartEnabledImage'] = $content["MENU_SELECTION_ENABLED"]; + else + $myChart['ChartEnabledImage'] = $content["MENU_SELECTION_DISABLED"]; + // --- + + // --- Set CSS Class if ( $i % 2 == 0 ) $myChart['cssclass'] = "line1"; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 40b7340..178e8d4 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -218,6 +218,47 @@ function CreateSourceTypesList( $selectedSource ) if ( $selectedSource == $content['SOURCETYPES'][SOURCE_PDO]['type'] ) { $content['SOURCETYPES'][SOURCE_PDO]['selected'] = "selected"; } else { $content['SOURCETYPES'][SOURCE_PDO]['selected'] = ""; } } +function CreateChartTypesList( $selectedChart ) +{ + global $content; + + // CHART_CAKE + $content['CHARTTYPES'][CHART_CAKE]['type'] = CHART_CAKE; + $content['CHARTTYPES'][CHART_CAKE]['DisplayName'] = $content['LN_CHART_TYPE_CAKE']; + if ( $selectedChart == $content['CHARTTYPES'][CHART_CAKE]['type'] ) { $content['CHARTTYPES'][CHART_CAKE]['selected'] = "selected"; } else { $content['CHARTTYPES'][CHART_CAKE]['selected'] = ""; } + + // CHART_BARS_VERTICAL + $content['CHARTTYPES'][CHART_BARS_VERTICAL]['type'] = CHART_BARS_VERTICAL; + $content['CHARTTYPES'][CHART_BARS_VERTICAL]['DisplayName'] = $content['LN_CHART_TYPE_BARS_VERTICAL']; + if ( $selectedChart == $content['CHARTTYPES'][CHART_BARS_VERTICAL]['type'] ) { $content['CHARTTYPES'][CHART_BARS_VERTICAL]['selected'] = "selected"; } else { $content['CHARTTYPES'][CHART_BARS_VERTICAL]['selected'] = ""; } + + // CHART_BARS_HORIZONTAL + $content['CHARTTYPES'][CHART_BARS_HORIZONTAL]['type'] = CHART_BARS_HORIZONTAL; + $content['CHARTTYPES'][CHART_BARS_HORIZONTAL]['DisplayName'] = $content['LN_CHART_TYPE_BARS_HORIZONTAL']; + if ( $selectedChart == $content['CHARTTYPES'][CHART_BARS_HORIZONTAL]['type'] ) { $content['CHARTTYPES'][CHART_BARS_HORIZONTAL]['selected'] = "selected"; } else { $content['CHARTTYPES'][CHART_BARS_HORIZONTAL]['selected'] = ""; } +} + +function CreateChartFields( $selectedChartField) +{ + global $content, $fields; + + // Process all fields + foreach ( $fields as $myField ) + { + $myFieldID = $myField['FieldID']; + + // Add new entry to array + $content['CHARTFIELDS'][$myFieldID]['ID'] = $myFieldID; + if ( isset($content[ $myField['FieldCaptionID'] ]) ) + $content['CHARTFIELDS'][$myFieldID]['DisplayName'] = $content[ $myField['FieldCaptionID'] ]; + else + $content['CHARTFIELDS'][$myFieldID]['DisplayName'] = $myFieldID; + + // set selected state + if ( $selectedChartField == $content['CHARTFIELDS'][$myFieldID]['ID'] ) { $content['CHARTFIELDS'][$myFieldID]['selected'] = "selected"; } else { $content['CHARTFIELDS'][$myFieldID]['selected'] = ""; } + } +} + function CreateDBTypesList( $selectedDBType ) { global $content; diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 25386ee..298605e 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -441,9 +441,23 @@ function LoadChartsFromDatabase() $myrows = DB_GetAllRows($result, true); if ( isset($myrows ) && count($myrows) > 0 ) { - // Overwrite Search Array with Database one - $CFG['Charts'] = $myrows; - $content['Charts'] = $myrows; + + // Overwrite existing Views array + unset($CFG['Charts']); + + // Loop through all data rows + foreach ($myrows as &$myChart ) + { + // Append to Chart Array + $CFG['Charts'][ $myChart['ID'] ] = $myChart; + } + + // Copy to content array! + $content['Charts'] = $CFG['Charts']; + +// // Overwrite Search Array with Database one +// $CFG['Charts'] = $myrows; +// $content['Charts'] = $myrows; } } @@ -488,9 +502,6 @@ function LoadViewsFromDatabase() { // Overwrite existing Views array unset($CFG['Views']); -// print_r ( $myrows ); -// exit; - // ReINIT Views Array InitViewConfigs(); diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index e537ea0..0f2f780 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -234,4 +234,18 @@ $content['LN_DBUPGRADE_ONESTATEMENTFAILED'] = "At least one statement failed, yo $content['LN_DBUPGRADE_ERRMSG'] = "Error Message"; $content['LN_DBUPGRADE_ULTRASTATSDBVERSION'] = "phpLogCon Database Version"; +// Charts Options +$content['LN_CHARTS_CENTER'] = "Charts Options"; +$content['LN_CHARTS_EDIT'] = "Edit Chart"; +$content['LN_CHARTS_DELETE'] = "Delete Chart"; +$content['LN_CHARTS_ADD'] = "Add new Chart"; +$content['LN_CHARTS_ADDEDIT'] = "Add / Edit a Chart"; +$content['LN_CHARTS_NAME'] = "Chart Name"; +$content['LN_CHARTS_ENABLED'] = "Chart enabled"; +$content['LN_CHARTS_ERROR_INVALIDORNOTFOUNDID'] = "The Chart-ID is invalid or could not be found."; +$content['LN_CHARTS_ERROR_IDNOTFOUND'] = "The Chart-ID could not be found in the database."; +$content['LN_CHARTS_WARNDELETESEARCH'] = "Are you sure that you want to delete the Chart '%1'? This cannot be undone!"; +$content['LN_CHARTS_ERROR_DELCHART'] = "Deleting of the Chart with id '%1' failed!"; +$content['LN_CHARTS_ERROR_HASBEENDEL'] = "The Chart '%1' has been successfully deleted!"; + ?> \ No newline at end of file diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index b26dfce..1e736f2 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -238,6 +238,20 @@ $content['LN_CHARTS_CENTER'] = "Charts Options"; $content['LN_CHARTS_EDIT'] = "Edit Chart"; $content['LN_CHARTS_DELETE'] = "Delete Chart"; $content['LN_CHARTS_ADD'] = "Add new Chart"; - +$content['LN_CHARTS_ADDEDIT'] = "Add / Edit a Chart"; +$content['LN_CHARTS_NAME'] = "Chart Name"; +$content['LN_CHARTS_ENABLED'] = "Chart enabled"; +$content['LN_CHARTS_ENABLEDONLY'] = "Enabled"; +$content['LN_CHARTS_ERROR_INVALIDORNOTFOUNDID'] = "The Chart-ID is invalid or could not be found."; +$content['LN_CHARTS_ERROR_IDNOTFOUND'] = "The Chart-ID could not be found in the database."; +$content['LN_CHARTS_WARNDELETESEARCH'] = "Are you sure that you want to delete the Chart '%1'? This cannot be undone!"; +$content['LN_CHARTS_ERROR_DELCHART'] = "Deleting of the Chart with id '%1' failed!"; +$content['LN_CHARTS_ERROR_HASBEENDEL'] = "The Chart '%1' has been successfully deleted!"; +$content['LN_CHARTS_ERROR_MISSINGPARAM'] = "The paramater '%1' is missing."; +$content['LN_CHARTS_HASBEENADDED'] = "The new Chart '%1' has been successfully added."; +$content['LN_CHARTS_ERROR_IDNOTFOUND'] = "The Chart-ID could not be found in the database."; +$content['LN_CHARTS_HASBEENEDIT'] = "The Chart '%1' has been successfully edited."; +$content['LN_CHARTS_ID'] = "ID"; +$content['LN_CHARTS_ASSIGNTO'] = "Assigned To"; ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index e0bfd19..a39cba1 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -301,4 +301,5 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; + ?> \ No newline at end of file diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index 553273c..f26ee40 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -233,4 +233,18 @@ $content['LN_DBUPGRADE_ONESTATEMENTFAILED'] = "At least one statement failed, yo $content['LN_DBUPGRADE_ERRMSG'] = "Error Message"; $content['LN_DBUPGRADE_ULTRASTATSDBVERSION'] = "phpLogCon Database Version"; +// Charts Options +$content['LN_CHARTS_CENTER'] = "Charts Options"; +$content['LN_CHARTS_EDIT'] = "Edit Chart"; +$content['LN_CHARTS_DELETE'] = "Delete Chart"; +$content['LN_CHARTS_ADD'] = "Add new Chart"; +$content['LN_CHARTS_ADDEDIT'] = "Add / Edit a Chart"; +$content['LN_CHARTS_NAME'] = "Chart Name"; +$content['LN_CHARTS_ENABLED'] = "Chart enabled"; +$content['LN_CHARTS_ERROR_INVALIDORNOTFOUNDID'] = "The Chart-ID is invalid or could not be found."; +$content['LN_CHARTS_ERROR_IDNOTFOUND'] = "The Chart-ID could not be found in the database."; +$content['LN_CHARTS_WARNDELETESEARCH'] = "Are you sure that you want to delete the Chart '%1'? This cannot be undone!"; +$content['LN_CHARTS_ERROR_DELCHART'] = "Deleting of the Chart with id '%1' failed!"; +$content['LN_CHARTS_ERROR_HASBEENDEL'] = "The Chart '%1' has been successfully deleted!"; + ?> \ No newline at end of file diff --git a/src/statistics.php b/src/statistics.php index 0277423..9794d13 100644 --- a/src/statistics.php +++ b/src/statistics.php @@ -85,7 +85,7 @@ if ( isset($content['Charts']) ) // --- // --- Set display name for chart field - if ( isset($myChart['chart_field']) && isset($content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]) ) + if ( isset($myChart['chart_field']) && isset($fields[$myChart['chart_field']]) && isset($content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]) ) $myChart['CHART_FIELD_DISPLAYNAME'] = $content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]; else $myChart['CHART_FIELD_DISPLAYNAME'] = $myChart['chart_field']; diff --git a/src/templates/admin/admin_charts.html b/src/templates/admin/admin_charts.html index 239b6d8..448df4b 100644 --- a/src/templates/admin/admin_charts.html +++ b/src/templates/admin/admin_charts.html @@ -1,34 +1,5 @@ - -

@@ -53,24 +24,25 @@
diff --git a/src/templates/install.html b/src/templates/install.html index 6673f67..328b42a 100644 --- a/src/templates/install.html +++ b/src/templates/install.html @@ -56,7 +56,7 @@
-

Installing phpLogCon Version {BUILDNUMBER} - Step {INSTALL_STEP}

+

{LN_INSTALL_TITLE}

@@ -80,10 +80,10 @@
-

Step 1 - Prerequisites

-

Before you start installing phpLogCon, the Installer setup has to check a few things first.
- You may have to correct some file permissions first.

- Click on to start the Test!

+

{LN_INSTALL_STEP1}

+

+ {LN_INSTALL_STEP1_TEXT} +

 

 

@@ -97,9 +97,9 @@
-

Step 2 - Verify File Permissions

-

The following file permissions have been checked. Verify the results below!
- You may use the configure.sh script from the contrib folder to set the permissions for you. +

{LN_INSTALL_STEP2}

+

+ {LN_INSTALL_STEP2_TEXT}

 

@@ -125,31 +125,31 @@
-

Step 3 - Basic Configuration

+

{LN_INSTALL_STEP3}

- In this step, you configure the basic configurations for phpLogCon. + {LN_INSTALL_STEP3_TEXT}

 

- + - + - + - + - + @@ -157,9 +157,9 @@ - + - +
Frontend Options
{LN_INSTALL_FRONTEND}
Number of syslog messages per page{LN_INSTALL_NUMOFSYSLOGS}
Message character limit for the main view{LN_INSTALL_MSGCHARLIMIT}
Show message details popup{LN_INSTALL_SHOWDETAILPOP} Yes No
Automatically resolved IP Addresses (inline){LN_INSTALL_AUTORESOLVIP} Yes No
 
User Database Options
{LN_INSTALL_USERDBOPTIONS}
Enable User Database{LN_INSTALL_ENABLEUSERDB} Yes No @@ -224,13 +224,9 @@ @@ -242,24 +238,26 @@
-

Step 4 - Create Tables

-

If you reached this step, the database connection has been successfully verified!

- The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
- WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database. -

- Click on to start the creation of the tables - +

{LN_INSTALL_STEP4}

+

+ {LN_INSTALL_STEP4_TEXT}

 

 

'; + // --- Set CSS Class + if ( $i % 2 == 0 ) + { + $myChart['cssclass'] = "line1"; + $myChart['rowbegin'] = ''; + } + else + { + $myChart['cssclass'] = "line2"; + $myChart['rowbegin'] = ''; + } + $i++; + // --- } - else - { - $myChart['cssclass'] = "line2"; - $myChart['rowbegin'] = ''; - } - $i++; - // --- } } @@ -137,5 +141,7 @@ $page -> parser($content, "statistics.html"); $page -> output(); // --- +//include($gl_root_path . 'include/functions_installhelpers.php'); +//ConvertCustomCharts(); ?> \ No newline at end of file diff --git a/src/templates/statistics.html b/src/templates/statistics.html index 35cf859..ec067f5 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -24,6 +24,7 @@ + {rowbegin}

-

Step 5 - Check SQL Results

-

Tables have been created. Check the List below for possible Error's

+

{LN_INSTALL_STEP5}

+

+ {LN_INSTALL_STEP5_TEXT} +

    -
  • Successfully executed statements: {sql_sucess}
    -
  • Failed statements: {sql_failed}
    +
  • {LN_INSTALL_SUCCESSSTATEMENTS} {sql_sucess}
    +
  • {LN_INSTALL_FAILEDSTATEMENTS} {sql_failed}
- You can now proceed to the next step adding the first phpLogCon Admin User! + {LN_INSTALL_STEP5_TEXT_NEXT}

 

- + - - + + @@ -281,23 +279,22 @@
At least one statement failed,see error reasons below{LN_INSTALL_STEP5_TEXT_FAILED}
Error MessageSQL Statement{LN_INSTALL_ERRORMSG}{LN_INSTALL_SQLSTATEMENT}
@@ -326,7 +323,7 @@
-

Step 6 - Creating the Main Useraccount

-

You are now about to create the initial phpLogCon User Account.
- This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center! +

{LN_INSTALL_STEP6}

+

+ {LN_INSTALL_STEP6_TEXT}

- - + - + - + - +
Create User Account
{LN_INSTALL_CREATEUSER}
Username{LN_LOGIN_USERNAME}
Password{LN_INSTALL_PASSWORD}
Repeat Password{LN_INSTALL_PASSWORDREPEAT}
@@ -316,7 +313,7 @@


-

Successfully created User '{MAIN_Username}'.

+

{LN_INSTALL_SUCCESSCREATED} '{MAIN_Username}'.


'; + } + else + { + $myChart['cssclass'] = "line2"; + $myChart['rowbegin'] = ''; + } + $i++; + // --- } - // Close file! - $stream->Close(); } -*/ +else +{ + // This will disable to Stats View and show an error message + $content['statsenabled'] = false; + // Set error code + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetErrorMessage(ERROR_CHARTS_NOTCONFIGURED); +} // --- // --- BEGIN CREATE TITLE diff --git a/src/templates/statistics.html b/src/templates/statistics.html index 1d955b3..67c6be0 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -12,32 +12,25 @@

-
-

Step 7 - Create the first source for syslog messages

+

{LN_INSTALL_STEP7}

 

@@ -447,11 +444,9 @@
@@ -464,7 +459,7 @@ - + - -
-

Step 8 - Done

-

Congratulations! You have successfully installed phpLogCon :D!

- To finish the Installation, remove the file install.php from the main directory!

- - Click here to go to your installation. +

{LN_INSTALL_STEP8}

+

+ {LN_INSTALL_STEP8_TEXT}

 

 

Install Progress: {LN_INSTALL_PROGRESS} @@ -477,10 +472,10 @@ - ReCheck + {LN_INSTALL_RECHECK} - Finish! + {LN_INSTALL_FINISH} +
From 6118df2262db9dcc2e7021d5d529a20b1785b9c5 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 23 Jul 2008 16:26:13 +0200 Subject: [PATCH 026/142] Fully Finishing the Converter and helper functions. --- src/admin/index.php | 20 +-- src/convert.php | 183 +++++++++++++++-------- src/include/functions_common.php | 133 ++++++++++------ src/include/functions_config.php | 3 +- src/include/functions_db.php | 63 ++++++-- src/include/functions_installhelpers.php | 135 +++++++++++++++++ src/install.php | 4 +- src/lang/en/main.php | 2 +- src/templates/convert.html | 16 +- 9 files changed, 409 insertions(+), 150 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index b804850..b683632 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -101,25 +101,7 @@ if ( isset($_POST['op']) && $content['EditAllowed'] ) if ( isset ($_POST['SearchCustomButtonSearch']) ) { $content['SearchCustomButtonSearch'] = DB_RemoveBadChars($_POST['SearchCustomButtonSearch']); } // Save configuration variables now - WriteConfigValue( "ViewDefaultLanguage", true ); - WriteConfigValue( "ViewDefaultTheme", true ); - - WriteConfigValue( "ViewUseTodayYesterday", true ); - WriteConfigValue( "ViewEnableDetailPopups", true ); - WriteConfigValue( "EnableIPAddressResolve", true ); - WriteConfigValue( "MiscShowDebugMsg", true ); - WriteConfigValue( "MiscShowDebugGridCounter", true ); - WriteConfigValue( "MiscShowPageRenderStats", true ); - WriteConfigValue( "MiscEnableGzipCompression", true ); - WriteConfigValue( "DebugUserLogin", true ); - - WriteConfigValue( "ViewMessageCharacterLimit", true ); - WriteConfigValue( "ViewEntriesPerPage", true ); - WriteConfigValue( "ViewEnableAutoReloadSeconds", true ); - - WriteConfigValue( "PrependTitle", true ); - WriteConfigValue( "SearchCustomButtonCaption", true ); - WriteConfigValue( "SearchCustomButtonSearch", true ); + SaveGeneralSettingsIntoDB(); // Do a redirect RedirectResult( $content['LN_GEN_SUCCESSFULLYSAVED'], "index.php" ); diff --git a/src/convert.php b/src/convert.php index b8cacfc..c3631e8 100644 --- a/src/convert.php +++ b/src/convert.php @@ -34,6 +34,7 @@ // *** Default includes and procedures *** // define('IN_PHPLOGCON', true); +define('IN_PHPLOGCON_CONVERT', true); // Extra for CONVERT Script! define('STEPSCRIPTNAME', "convert.php"); // Helper variable for the STEP helper functions $gl_root_path = './'; @@ -114,12 +115,12 @@ if ( $content['CONVERT_STEP'] == 2 ) // Check the database connect $link_id = mysql_connect( $CFG['UserDBServer'], $CFG['UserDBUser'], $CFG['UserDBPass']); if (!$link_id) - RevertOneStep( $content['INSTALL_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORCONNECTFAILED'], $CFG['UserDBServer']) . "
" . DB_ReturnSimpleErrorMsg() ); + RevertOneStep( $content['CONVERT_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORCONNECTFAILED'], $CFG['UserDBServer']) . "
" . DB_ReturnSimpleErrorMsg() ); // Try to select the DB! $db_selected = mysql_select_db($CFG['UserDBName'], $link_id); if(!$db_selected) - RevertOneStep( $content['INSTALL_STEP']-1,GetAndReplaceLangStr( $content['LN_INSTALL_ERRORACCESSDENIED'], $CFG['UserDBName']) . "
" . DB_ReturnSimpleErrorMsg()); + RevertOneStep( $content['CONVERT_STEP']-1,GetAndReplaceLangStr( $content['LN_INSTALL_ERRORACCESSDENIED'], $CFG['UserDBName']) . "
" . DB_ReturnSimpleErrorMsg()); } @@ -129,79 +130,141 @@ else if ( $content['CONVERT_STEP'] == 3 ) $content['sql_sucess'] = 0; $content['sql_failed'] = 0; - // Import default database if user db is enabled! - if ( $_SESSION['UserDBEnabled'] == 1 ) + // Init $totaldbdefs + $totaldbdefs = ""; + + // Read the table GLOBAL definitions + ImportDataFile( $content['BASEPATH'] . "include/db_template.txt" ); + + // Process definitions ^^ + if ( strlen($totaldbdefs) <= 0 ) { - // Init $totaldbdefs - $totaldbdefs = ""; + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINVALIDDBFILE'], $content['BASEPATH'] . "include/db_template.txt"); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; + $content['sql_failed']++; + } - // Read the table GLOBAL definitions - ImportDataFile( $content['BASEPATH'] . "include/db_template.txt" ); - - // Process definitions ^^ - if ( strlen($totaldbdefs) <= 0 ) - { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINVALIDDBFILE'], $content['BASEPATH'] . "include/db_template.txt"); - $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; - $content['sql_failed']++; - } - - // Replace stats_ with the custom one ;) - $totaldbdefs = str_replace( "`logcon_", "`" . $_SESSION["UserDBPref"], $totaldbdefs ); - - // Now split by sql command - $mycommands = split( ";\n", $totaldbdefs ); - + // Replace stats_ with the custom one ;) + $totaldbdefs = str_replace( "`logcon_", "`" . $CFG["UserDBPref"], $totaldbdefs ); + + // Now split by sql command + $mycommands = split( ";\n", $totaldbdefs ); + // // check for different linefeed // if ( count($mycommands) <= 1 ) // $mycommands = split( ";\n", $totaldbdefs ); - //Still only one? Abort - if ( count($mycommands) <= 1 ) + //Still only one? Abort + if ( count($mycommands) <= 1 ) + { + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINSQLCOMMANDS'], $content['BASEPATH'] . "include/db_template.txt"); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; + $content['sql_failed']++; + } + + // Append INSERT Statement for Config Table to set the Database Version ^^! + $mycommands[count($mycommands)] = "INSERT INTO `" . $CFG["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', 1)"; + + // --- Now execute all commands + ini_set('error_reporting', E_WARNING); // Enable Warnings! + + // Establish DB Connection + DB_Connect(); + + for($i = 0; $i < count($mycommands); $i++) + { + if ( strlen(trim($mycommands[$i])) > 1 ) { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = GetAndReplaceLangStr( $content['LN_INSTALL_ERRORINSQLCOMMANDS'], $content['BASEPATH'] . "include/db_template.txt"); - $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = ""; - $content['sql_failed']++; - } - - // Append INSERT Statement for Config Table to set the GameVersion and Database Version ^^! - $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '1', 1)"; - - // --- Now execute all commands - ini_set('error_reporting', E_WARNING); // Enable Warnings! - InitUserDbSettings(); - - // Establish DB Connection - DB_Connect(); - - for($i = 0; $i < count($mycommands); $i++) - { - if ( strlen(trim($mycommands[$i])) > 1 ) + $result = DB_Query( $mycommands[$i], false ); + if ($result == FALSE) { - $result = DB_Query( $mycommands[$i], false ); - if ($result == FALSE) - { - $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = DB_ReturnSimpleErrorMsg(); - $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = $mycommands[$i]; + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = DB_ReturnSimpleErrorMsg(); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = $mycommands[$i]; - // --- Set CSS Class - if ( $content['sql_failed'] % 2 == 0 ) - $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line1"; - else - $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line2"; - // --- - - $content['sql_failed']++; - } + // --- Set CSS Class + if ( $content['sql_failed'] % 2 == 0 ) + $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line1"; else - $content['sql_sucess']++; + $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line2"; + // --- - // Free result - DB_FreeQuery($result); + $content['sql_failed']++; } + else + $content['sql_sucess']++; + + // Free result + DB_FreeQuery($result); } } } +else if ( $content['CONVERT_STEP'] == 4 ) +{ + if ( isset($_SESSION['MAIN_Username']) ) + $content['MAIN_Username'] = $_SESSION['MAIN_Username']; + else + $content['MAIN_Username'] = ""; + + $content['MAIN_Password1'] = ""; + $content['MAIN_Password2'] = ""; + + // Check for Error Msg + if ( isset($_GET['errormsg']) ) + { + $content['iserror'] = "true"; + $content['errormsg'] = DB_RemoveBadChars( urldecode($_GET['errormsg']) ); + } +} +else if ( $content['CONVERT_STEP'] == 5 ) +{ + // Verify Username and Password Input + if ( isset($_POST['username']) ) + $_SESSION['MAIN_Username'] = DB_RemoveBadChars($_POST['username']); + else + RevertOneStep( $content['CONVERT_STEP']-1, $content['LN_INSTALL_MISSINGUSERNAME'] ); + + if ( isset($_POST['password1']) ) + $_SESSION['MAIN_Password1'] = DB_RemoveBadChars($_POST['password1']); + else + $_SESSION['MAIN_Password1'] = ""; + + if ( isset($_POST['password2']) ) + $_SESSION['MAIN_Password2'] = DB_RemoveBadChars($_POST['password2']); + else + $_SESSION['MAIN_Password2'] = ""; + + if ( + strlen($_SESSION['MAIN_Password1']) < 4 || + $_SESSION['MAIN_Password1'] != $_SESSION['MAIN_Password2'] + ) + RevertOneStep( $content['CONVERT_STEP']-1, $content['LN_INSTALL_PASSWORDNOTMATCH'] ); + + // --- Now execute all commands + ini_set('error_reporting', E_WARNING); // Enable Warnings! + + // Establish DB Connection + DB_Connect(); + + // Everything is fine, lets go create the User! + CreateUserName( $_SESSION['MAIN_Username'], $_SESSION['MAIN_Password1'], 1 ); + + // Show User success! + $content['MAIN_Username'] = $_SESSION['MAIN_Username']; + $content['createduser'] = true; +} +else if ( $content['CONVERT_STEP'] == 6 ) +{ + // To be on the save side, establish DB Connection + DB_Connect(); + + // Perform conversion of settings into the database now! + ConvertCustomSearches(); + ConvertCustomViews(); + ConvertCustomSources(); + + // Import General Settings in the last step! + ConvertGeneralSettings(); +} // --- // --- Parsen and Output diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 68569ec..3e1512d 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -483,61 +483,70 @@ function GetAndReplaceLangStr( $strlang, $param1 = "", $param2 = "", $param3 = " function InitConfigurationValues() { global $content, $CFG, $LANG, $gl_root_path; - - // If Database is enabled, try to read from database! - if ( $CFG['UserDBEnabled'] ) + + // To avoid this code in case of conversion + if ( !defined('IN_PHPLOGCON_CONVERT') ) { - // Get configuration variables - $result = DB_Query("SELECT * FROM " . DB_CONFIG . " WHERE is_global = true"); - $rows = DB_GetAllRows($result, true, true); - - // Read results from DB and overwrite in $CFG Array! - if ( isset($rows ) ) + // If Database is enabled, try to read from database! + if ( $CFG['UserDBEnabled'] ) { - for($i = 0; $i < count($rows); $i++) + // Get configuration variables + $result = DB_Query("SELECT * FROM " . DB_CONFIG . " WHERE is_global = true"); + + if ( $result ) { - $CFG[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; - $content[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; + $rows = DB_GetAllRows($result, true, true); + // Read results from DB and overwrite in $CFG Array! + if ( isset($rows ) ) + { + for($i = 0; $i < count($rows); $i++) + { + $CFG[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; + $content[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; + } + } + } + else // Critical ERROR HERE! + DieWithFriendlyErrorMsg( "Critical Error occured while trying to access the database in table '" . DB_CONFIG . "'" ); + + // Now we init the user session stuff + InitUserSession(); + + // Check if user needs to be logged in + if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + { + if ( !$content['SESSION_LOGGEDIN'] ) + { + // User needs to be logged in, redirect to login page + if ( !defined("IS_NOLOGINPAGE") ) + RedirectToUserLogin(); + } + } + else if ( defined('IS_ADMINPAGE') ) // Language System not initialized yet + DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); + + // Load Configured Searches + LoadSearchesFromDatabase(); + + // Load Configured Views + LoadViewsFromDatabase(); + + // Load Configured Sources + LoadSourcesFromDatabase(); + + + // Database Version Checker! + if ( $content['database_internalversion'] > $content['database_installedversion'] ) + { + // Database is out of date, we need to upgrade + $content['database_forcedatabaseupdate'] = "yes"; } } - - // Now we init the user session stuff - InitUserSession(); - - // Check if user needs to be logged in - if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + else { - if ( !$content['SESSION_LOGGEDIN'] ) - { - // User needs to be logged in, redirect to login page - if ( !defined("IS_NOLOGINPAGE") ) - RedirectToUserLogin(); - } + if ( defined('IS_ADMINPAGE') || defined("IS_NOLOGINPAGE") ) // Language System not initialized yet + DieWithFriendlyErrorMsg( "The phpLogCon user system is currently disabled or not installed." ); } - else if ( defined('IS_ADMINPAGE') ) // Language System not initialized yet - DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); - - // Load Configured Searches - LoadSearchesFromDatabase(); - - // Load Configured Views - LoadViewsFromDatabase(); - - // Load Configured Sources - LoadSourcesFromDatabase(); - - - // Database Version Checker! - if ( $content['database_internalversion'] > $content['database_installedversion'] ) - { - // Database is out of date, we need to upgrade - $content['database_forcedatabaseupdate'] = "yes"; - } - } - else - { - if ( defined('IS_ADMINPAGE') || defined("IS_NOLOGINPAGE") ) // Language System not initialized yet - DieWithFriendlyErrorMsg( "The phpLogCon user system is currently disabled or not installed." ); } // --- Language Handling @@ -661,7 +670,7 @@ function CheckUrlOrIP($ip) function DieWithErrorMsg( $szerrmsg ) { - global $content; + global $gl_root_path, $content; print("phpLogCon :: Critical Error occured"); print("

Critical Error occured


"); print("Errordetails:
" . $szerrmsg); @@ -1000,5 +1009,31 @@ function PrintSecureUserCheck( $warningtext, $yesmsg, $nomsg ) exit; } +function SaveGeneralSettingsIntoDB() +{ + WriteConfigValue( "ViewDefaultLanguage", true ); + WriteConfigValue( "ViewDefaultTheme", true ); + + WriteConfigValue( "ViewUseTodayYesterday", true ); + WriteConfigValue( "ViewEnableDetailPopups", true ); + WriteConfigValue( "EnableIPAddressResolve", true ); + WriteConfigValue( "MiscShowDebugMsg", true ); + WriteConfigValue( "MiscShowDebugGridCounter", true ); + WriteConfigValue( "MiscShowPageRenderStats", true ); + WriteConfigValue( "MiscEnableGzipCompression", true ); + WriteConfigValue( "DebugUserLogin", true ); + + WriteConfigValue( "ViewMessageCharacterLimit", true ); + WriteConfigValue( "ViewEntriesPerPage", true ); + WriteConfigValue( "ViewEnableAutoReloadSeconds", true ); + + WriteConfigValue( "PrependTitle", true ); + WriteConfigValue( "SearchCustomButtonCaption", true ); + WriteConfigValue( "SearchCustomButtonSearch", true ); + + // Extra Fields + WriteConfigValue( "DefaultViewsID", true ); + WriteConfigValue( "DefaultSourceID", true ); +} ?> \ No newline at end of file diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 632aafe..06f5476 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -83,13 +83,12 @@ function InitSourceConfigs() } else { - if ( isset($mysource['ViewID']) ) + if ( isset($mysource['ViewID']) && strlen($mysource['ViewID']) > 0 ) // Set to configured Source ViewID $content['Sources'][$iSourceID]['ViewID'] = $mysource['ViewID']; else // Not configured, maybe old legacy cfg. Set default view. $content['Sources'][$iSourceID]['ViewID'] = strlen($CFG['DefaultViewsID']) > 0 ? $CFG['DefaultViewsID'] : "SYSLOG"; - } // Only for the display box diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 11abdb3..228451e 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -53,6 +53,10 @@ function DB_Connect() { global $userdbconn, $CFG; + // Avoid if already OPEN + if ($userdbconn) + return; + //TODO: Check variables first $userdbconn = mysql_connect($CFG['UserDBServer'],$CFG['UserDBUser'],$CFG['UserDBPass']); if (!$userdbconn) @@ -225,16 +229,35 @@ function DB_PrintError($MyErrorMsg, $DieOrNot) function DB_RemoveParserSpecialBadChars($myString) { -// DO NOT REPLACD! $returnstr = str_replace("\\","\\\\",$myString); +// DO NOT REPLACE! $returnstr = str_replace("\\","\\\\",$myString); $returnstr = str_replace("'","\\'",$myString); return $returnstr; } function DB_RemoveBadChars($myString) { + // Replace with internal PHP Functions! + if ( !get_magic_quotes_runtime() ) + return addslashes($myString); + else + return $myString; +/* $returnstr = str_replace("\\","\\\\",$myString); $returnstr = str_replace("'","\\'",$returnstr); return $returnstr; +*/ +} + +function DB_ReturnLastInsertID($myResult = false) +{ + // --- Abort in this case! + global $CFG; + if ( $CFG['UserDBEnabled'] == false ) + return; + // --- + + global $userdbconn; + return mysql_insert_id($userdbconn); } function DB_GetRowCount($query) @@ -279,7 +302,16 @@ function DB_Exec($query) return false; } -function WriteConfigValue($szValue, $is_global = true, $userid = false, $groupid = false) +function PrepareValueForDB($szValue) +{ + // Copy value for DB and check for BadDB Chars! + if ( preg_match("/(? &$mySearch) + { + // New Entry + $result = DB_Query("INSERT INTO " . DB_SEARCHES . " (DisplayName, SearchQuery) VALUES ( '" . PrepareValueForDB($mySearch['DisplayName']) . "', '" . PrepareValueForDB($mySearch['SearchQuery']) . "')"); + $mySearch['DBID'] = DB_ReturnLastInsertID($result); + DB_FreeQuery($result); + + } +} + +function ConvertCustomViews() +{ + global $CFG, $content; + + // Insert all searches into the DB! + foreach($CFG['Views'] as $viewid => &$myView) + { + if ( is_numeric($viewid) || $viewid == "LEGACY" ) + { + // Create Columns String + foreach ($myView['Columns'] as $myCol ) + { + if ( isset($myView['ColumnsAsString']) ) + $myView['ColumnsAsString'] .= ", " . $myCol; + else + $myView['ColumnsAsString'] = $myCol; + } + + // New Entry + $result = DB_Query("INSERT INTO " . DB_VIEWS . " (DisplayName, Columns) VALUES ( '" . PrepareValueForDB($myView['DisplayName']) . "', '" . PrepareValueForDB($myView['ColumnsAsString']) . "')"); + $myView['DBID'] = DB_ReturnLastInsertID($result); + DB_FreeQuery($result); + } + } + + // --- Check and set DefaultViewID! + if ( + (isset($content['DefaultViewsID']) && strlen($content['DefaultViewsID']) > 0) + && + (isset($CFG['Views'][$content['DefaultViewsID']]['DBID'])) + ) + { + // Copy the new DefaultViewID back! + $content['DefaultViewsID'] = $CFG['Views'][$content['DefaultViewsID']]['DBID']; + $CFG['DefaultViewsID'] = $content['DefaultViewsID']; + } + // --- +} + +function ConvertCustomSources() +{ + global $CFG, $content; + + // Insert all searches into the DB! + foreach($CFG['Sources'] as $sourceid => &$mySource) + { + // Correct VIEWID! + if ( isset($mySource['ViewID']) ) + { + if ( isset($CFG['Views'][$mySource['ViewID']]['DBID']) ) + $mySource['ViewID'] = $CFG['Views'][$mySource['ViewID']]['DBID']; + } + else + $mySource['ViewID'] = ""; // Set empty default + + // Add New Entry + if ( $mySource['SourceType'] == SOURCE_DISK ) + { + $result = DB_Query("INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, LogLineType, DiskFile) VALUES ( " . + "'" . PrepareValueForDB($mySource['Name']) . "', " . + " " . PrepareValueForDB($mySource['SourceType']) . " , " . + "'" . PrepareValueForDB($mySource['ViewID']) . "', " . + "'" . PrepareValueForDB($mySource['LogLineType']) . "', " . + "'" . PrepareValueForDB($mySource['DiskFile']) . "'" . + ")"); + } + else if ( $mySource['SourceType'] == SOURCE_DB || $mySource['SourceType'] == SOURCE_PDO ) + { + // Set Default for number fields + if ( !isset($mySource['DBEnableRowCounting']) ) + $mySource['DBEnableRowCounting'] = 0; + else // Force to number + $mySource['DBEnableRowCounting'] = intval($mySource['DBEnableRowCounting']); + if ( !isset($mySource['DBType']) ) + $mySource['DBType'] = DB_MYSQL; + + // Perform the insert + $result = DB_Query("INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting) VALUES ( " . + "'" . PrepareValueForDB($mySource['Name']) . "', " . + " " . PrepareValueForDB($mySource['SourceType']) . " , " . + "'" . PrepareValueForDB($mySource['ViewID']) . "', " . + "'" . PrepareValueForDB($mySource['DBTableType']) . "', " . + " " . PrepareValueForDB($mySource['DBType']) . " , " . + "'" . PrepareValueForDB($mySource['DBServer']) . "', " . + "'" . PrepareValueForDB($mySource['DBName']) . "', " . + "'" . PrepareValueForDB($mySource['DBUser']) . "', " . + "'" . PrepareValueForDB($mySource['DBPassword']) . "', " . + "'" . PrepareValueForDB($mySource['DBTableName']) . "', " . + " " . PrepareValueForDB($mySource['DBEnableRowCounting']) . " " . + ")"); + } + else + DieWithFriendlyErrorMsg( GetAndReplaceLangStr($content['LN_CONVERT_ERROR_SOURCEIMPORT'], $mySource['SourceType']) ); + + // Copy DBID! + $mySource['DBID'] = DB_ReturnLastInsertID($result); + DB_FreeQuery($result); + } + + // --- Check and set DefaultSourceID! + if ( + (isset($content['DefaultSourceID']) && strlen($content['DefaultSourceID']) > 0) + && + (isset($CFG['Sources'][$content['DefaultSourceID']]['DBID'])) + ) + { + // Copy the new DefaultSourceID back! + $content['DefaultSourceID'] = $CFG['Sources'][$content['DefaultSourceID']]['DBID']; + $CFG['DefaultSourceID'] = $content['DefaultSourceID']; + } + // --- +} // --- ?> \ No newline at end of file diff --git a/src/install.php b/src/install.php index 59ec737..ce41cff 100644 --- a/src/install.php +++ b/src/install.php @@ -398,8 +398,8 @@ else if ( $content['INSTALL_STEP'] == 5 ) $content['sql_failed']++; } - // Append INSERT Statement for Config Table to set the GameVersion and Database Version ^^! - $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '1', 1)"; + // Append INSERT Statement for Config Table to set the Database Version ^^! + $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', 1)"; // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 83f6cfc..cd306fb 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -244,7 +244,7 @@ $content['LN_CONVERT_STEP5_TEXT'] = ' to $content['LN_CONVERT_STEP6'] = "Step 8 - Done"; $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; -$content['LN_CONVERT_'] = ""; +$content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; $content['LN_CONVERT_'] = ""; $content['LN_CONVERT_'] = ""; $content['LN_CONVERT_'] = ""; diff --git a/src/templates/convert.html b/src/templates/convert.html index 2a65068..d4030a2 100644 --- a/src/templates/convert.html +++ b/src/templates/convert.html @@ -77,7 +77,7 @@
- +
@@ -116,9 +116,9 @@
- + - +
@@ -149,9 +149,9 @@
- + - + @@ -183,9 +183,9 @@ // Manually perform initial Javascript Calls toggleSourceTypeVisibility('SourceType'); - + - +
@@ -197,7 +197,7 @@
- + From 6864de02aa6db93d3aedac7d82678ef5809f6e98 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 23 Jul 2008 17:14:16 +0200 Subject: [PATCH 027/142] Fixed minor issues with the installer and my last changes --- src/include/functions_common.php | 20 +++++++++++-------- src/include/functions_db.php | 2 +- src/install.php | 27 +++++++++++++++++++------- src/lang/en/main.php | 2 +- src/templates/admin/admin_sources.html | 6 +++--- src/templates/install.html | 2 +- 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 3e1512d..c38eb73 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -512,18 +512,22 @@ function InitConfigurationValues() // Now we init the user session stuff InitUserSession(); - // Check if user needs to be logged in - if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + if ( !$content['SESSION_LOGGEDIN'] ) { - if ( !$content['SESSION_LOGGEDIN'] ) + + // Check if user needs to be logged in + if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) { - // User needs to be logged in, redirect to login page - if ( !defined("IS_NOLOGINPAGE") ) - RedirectToUserLogin(); + // User needs to be logged in, redirect to login page + if ( !defined("IS_NOLOGINPAGE") ) + RedirectToUserLogin(); + } + else if ( defined('IS_ADMINPAGE') ) + { + // Language System not initialized yet + DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); } } - else if ( defined('IS_ADMINPAGE') ) // Language System not initialized yet - DieWithFriendlyErrorMsg( "You need to be logged in in order to access the admin pages." ); // Load Configured Searches LoadSearchesFromDatabase(); diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 228451e..6484251 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -305,7 +305,7 @@ function DB_Exec($query) function PrepareValueForDB($szValue) { // Copy value for DB and check for BadDB Chars! - if ( preg_match("/(?
If you want to reconfigure phpLogCon, either delete the current config.php or replace it with an empty file.

Click here to return to pgpLogCon start page.'; $content['LN_INSTALL_FILEORDIRNOTWRITEABLE'] = "At least one file or directory (or more) is not writeable, please check the file permissions (chmod 666)!"; diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index c6c6e6f..910472f 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -17,14 +17,14 @@ showvisibility("HiddenDatabaseTypeOptions"); hidevisibility("HiddenDiskTypeOptions"); - hidevisibility("HiddenDBTYpesOptions"); + hidevisibility("HiddenDBTypesOptions"); } else if (myfield.value == 3) { showvisibility("HiddenDatabaseTypeOptions"); hidevisibility("HiddenDiskTypeOptions"); - showvisibility("HiddenDBTYpesOptions"); + showvisibility("HiddenDBTypesOptions"); } } @@ -158,7 +158,7 @@
{LN_CFG_DATABASETYPEOPTIONS}
-
+
diff --git a/src/templates/install.html b/src/templates/install.html index 328b42a..61f7641 100644 --- a/src/templates/install.html +++ b/src/templates/install.html @@ -56,7 +56,7 @@
{LN_CFG_DBSTORAGEENGINE}
-

{LN_INSTALL_TITLE}

+

{LN_INSTALL_TITLETOP}

From 6d4465f4a2792d96e343a13d8bba6c41ddbcdd66 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 23 Jul 2008 17:24:44 +0200 Subject: [PATCH 028/142] Fixed bug in sources admin --- src/css/defaults.css | 6 ++++++ src/templates/admin/admin_sources.html | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/css/defaults.css b/src/css/defaults.css index 9f49765..7bd5032 100644 --- a/src/css/defaults.css +++ b/src/css/defaults.css @@ -22,6 +22,12 @@ display: none; } +.ShownContent +{ + visibility: visible; + display: inline; +} + .SelectSavedFilter { margin-top: 2px; diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index 910472f..b18d0cc 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -130,10 +130,11 @@

-
+
@@ -153,12 +154,12 @@
{LN_SOURCES_DISKTYPEOPTIONS}
-
+
{LN_CFG_DATABASETYPEOPTIONS}
-
+
@@ -208,7 +209,6 @@
{LN_CFG_DBSTORAGEENGINE}
- + + + + + + + + + + + From 8a9a652c9faeae58b945bce34aad0dfc594792b4 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 24 Jul 2008 15:35:09 +0200 Subject: [PATCH 031/142] Not really changed something --- src/include/functions_common.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index c38eb73..20391f6 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -899,7 +899,6 @@ function AddContextLinks(&$sourceTxt) $search = array ( '/\.([\w\d\_\-]+)\.(' . $szTLDDomains . ')([^a-zA-Z0-9\.])/x', -// '|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|', /* (?:127)| */ '/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/x', ); From fa0b946eb5c49de5246ffd0ccc65ec91ed783d07 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 24 Jul 2008 15:42:54 +0200 Subject: [PATCH 032/142] Added changelog entry --- ChangeLog | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index f306ffd..e427894 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,13 +1,23 @@ --------------------------------------------------------------------------- -<<<<<<< HEAD:ChangeLog -Version 2.5.0 (devel), 2008-06-10 -- Moved older devel branch to beta branch. Increment Version minor number. -======= +Version 2.5.0 (devel), 2008-07-24 +- Fully implemented the UserDB System. This is the next major milestone + in the development of phpLogCon. The new UserDB System allows you to + fully customize phpLogCon using an admin interface and offers the + following sub-features: Users, Groups, Sources, Views, Searches and + general options. Exiting configurations can be imported into the + user system using the convert.php script. The installer has an option + to install the user system. +- Added Icons to all Topmenu entries, as well as into the Admin + Topmenu entries. +- Improved stylesheets +--------------------------------------------------------------------------- Version 2.3.7 (beta), 2008-07-07 - Added missing db mapping for program field of syslogng - thanks to Micha "Wolvverine" Panasiewicz - Added translation for Brazilian Portuguese, thanks to Ricardo Maraschini ->>>>>>> 3e647291ba2e5cba972a52679cd8640db0a6614f:ChangeLog +--------------------------------------------------------------------------- +Version 2.5.0 (devel), 2008-06-10 +- Moved older devel branch to beta branch. Increment Version minor number. --------------------------------------------------------------------------- Version 2.3.6 (devel), 2008-06-09 - Added new feature, multiple configureable views which can be configured From c8c1e45226d01e44da19f87c1e62a16a68e132e1 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 25 Jul 2008 13:09:02 +0200 Subject: [PATCH 033/142] Fixed a problem if predefined columns in the list when adding a new View --- src/admin/views.php | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/admin/views.php b/src/admin/views.php index 6ef2444..06a3580 100644 --- a/src/admin/views.php +++ b/src/admin/views.php @@ -103,7 +103,7 @@ if ( isset($_GET['op']) ) $content['DisplayName'] = $myview['DisplayName'] ; $content['userid'] = $myview['userid']; - $content['Columns'] = $myview['Columns']; + $content['COLUMNS'] = $myview['Columns']; if ( $content['userid'] != null ) $content['CHECKED_ISUSERONLY'] = "checked"; else @@ -194,9 +194,8 @@ if ( isset($content['ISEDITORNEWVIEW']) && $content['ISEDITORNEWVIEW'] ) // If Columns are send using POST we use them, otherwise we try to use from the view itself, if available if ( isset($_POST['Columns']) ) $AllColumns = $_POST['Columns']; - else if ( isset($content['Columns']) ) - $AllColumns = $content['Columns']; - + else if ( isset($content['COLUMNS']) ) + $AllColumns = $content['COLUMNS']; // Read Columns from FORM data! if ( isset($AllColumns) ) @@ -406,16 +405,16 @@ if ( isset($_POST['op']) ) // Copy columns ID's foreach ($_POST['Columns'] as $myColKey) { - if ( isset($content['Columns']) ) - $content['Columns'] .= ", " . $myColKey; + if ( isset($content['COLUMNS']) ) + $content['COLUMNS'] .= ", " . $myColKey; else - $content['Columns'] = $myColKey; + $content['COLUMNS'] = $myColKey; } // Add custom search now! $sqlquery = "INSERT INTO " . DB_VIEWS. " (DisplayName, Columns, userid, groupid) VALUES ('" . $content['DisplayName'] . "', - '" . $content['Columns'] . "', + '" . $content['COLUMNS'] . "', " . $content['userid'] . ", " . $content['groupid'] . " )"; @@ -446,20 +445,20 @@ if ( isset($_POST['op']) ) if ( isset($_POST['Columns']) && is_array($_POST['Columns']) ) { // Copy columns ID's - unset($content['Columns']); + unset($content['COLUMNS']); foreach ($_POST['Columns'] as $myColKey) { - if ( isset($content['Columns']) ) - $content['Columns'] .= ", " . $myColKey; + if ( isset($content['COLUMNS']) ) + $content['COLUMNS'] .= ", " . $myColKey; else - $content['Columns'] = $myColKey; + $content['COLUMNS'] = $myColKey; } // Edit the Search Entry now! $result = DB_Query("UPDATE " . DB_VIEWS . " SET DisplayName = '" . $content['DisplayName'] . "', - Columns = '" . $content['Columns'] . "', + Columns = '" . $content['COLUMNS'] . "', userid = " . $content['userid'] . ", groupid = " . $content['groupid'] . " WHERE ID = " . $content['VIEWID']); From bdf77d52080d41cdba9fcfd4b3443e868f2d192e Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 25 Jul 2008 13:25:05 +0200 Subject: [PATCH 034/142] Enhanced error display in all admin templates. Fixed minor issues. --- src/admin/views.php | 9 ++++++++- src/lang/de/main.php | 2 ++ src/lang/en/main.php | 4 +++- src/lang/pt_BR/main.php | 3 ++- src/templates/admin/admin_groups.html | 17 +++++++++-------- src/templates/admin/admin_searches.html | 5 ++++- src/templates/admin/admin_sources.html | 5 ++++- src/templates/admin/admin_users.html | 5 ++++- src/templates/admin/admin_views.html | 7 +++++-- src/templates/details.html | 2 +- src/templates/index.html | 2 +- src/themes/dark/main.css | 3 --- src/themes/default/main.css | 3 --- 13 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/admin/views.php b/src/admin/views.php index 06a3580..fdff64e 100644 --- a/src/admin/views.php +++ b/src/admin/views.php @@ -58,6 +58,9 @@ IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); //if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) // DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); +// Init helper variable to empty string +$content['FormUrlAddOP'] = ""; + if ( isset($_GET['op']) ) { if ($_GET['op'] == "add") @@ -72,6 +75,7 @@ if ( isset($_GET['op']) ) $content['userid'] = null; $content['CHECKED_ISUSERONLY'] = ""; $content['VIEWID'] = ""; + $content['FormUrlAddOP'] = "?op=add"; // --- Check if groups are available $content['SUBGROUPS'] = GetGroupsForSelectfield(); @@ -88,7 +92,6 @@ if ( isset($_GET['op']) ) $content['VIEW_FORMACTION'] = "editview"; $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_EDIT']; - // Copy Views array for further modifications $content['VIEWS'] = $content['Views']; @@ -99,6 +102,10 @@ if ( isset($_GET['op']) ) $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); if ( isset($content['VIEWS'][ $content['VIEWID'] ]) ) { + + //Set the FormAdd URL + $content['FormUrlAddOP'] = "?op=edit&id=" . $content['VIEWID']; + $myview = $content['VIEWS'][ $content['VIEWID'] ]; $content['DisplayName'] = $myview['DisplayName'] ; diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 50ed8bb..e126ffd 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -57,6 +57,8 @@ $content['LN_GEN_SOURCE_DB'] = "Datenbank"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; + $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; + $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index cf3e7ca..8fc40c2 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -58,7 +58,9 @@ $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; - + $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; + $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; $content['LN_MENU_SHOWEVENTS'] = "Show Events"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 0c869c3..abb861e 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -61,7 +61,8 @@ $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Visão"; $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; - $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; + $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; + $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/admin/admin_groups.html b/src/templates/admin/admin_groups.html index 5004c9f..39fb83f 100644 --- a/src/templates/admin/admin_groups.html +++ b/src/templates/admin/admin_groups.html @@ -1,19 +1,20 @@ + +
+
+

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

+{LN_GEN_ERRORRETURNPREV} +
+
+ +
From 7d87598c417dffaa51339defe32e31bef84cfaa9 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 23 Jul 2008 17:31:00 +0200 Subject: [PATCH 029/142] Fixed Windows linefeeds in new code files. Added admin lang file into de and pt_BR translation. --- src/admin/groups.php | 986 ++++++++++++++++---------------- src/admin/result.php | 172 +++--- src/admin/searches.php | 658 +++++++++++----------- src/admin/sources.php | 1150 ++++++++++++++++++------------------- src/admin/users.php | 764 ++++++++++++------------- src/admin/views.php | 1152 +++++++++++++++++++------------------- src/lang/de/admin.php | 211 +++++++ src/lang/de/main.php | 74 +++ src/lang/en/admin.php | 3 - src/lang/pt_BR/admin.php | 211 +++++++ src/lang/pt_BR/main.php | 74 +++ src/login.php | 220 ++++---- 12 files changed, 3121 insertions(+), 2554 deletions(-) create mode 100644 src/lang/de/admin.php create mode 100644 src/lang/pt_BR/admin.php diff --git a/src/admin/groups.php b/src/admin/groups.php index b733750..18c45e5 100644 --- a/src/admin/groups.php +++ b/src/admin/groups.php @@ -1,494 +1,494 @@ - Helps administrating groups - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './../'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); - -// Set PAGE to be ADMINPAGE! -define('IS_ADMINPAGE', true); -$content['IS_ADMINPAGE'] = true; -InitPhpLogCon(); -InitSourceConfigs(); -InitFrontEndDefaults(); // Only in WebFrontEnd -InitFilterHelpers(); // Helpers for frontend filtering! - -// Init admin langauge file now! -IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); -// --- - -// --- BEGIN Custom Code - -// Only if the user is an admin! -if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); - -if ( isset($_GET['op']) ) -{ - if ($_GET['op'] == "add") - { - // Set Mode to add - $content['ISEDITORNEWGROUP'] = "true"; - $content['GROUP_FORMACTION'] = "addnewgroup"; - $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_ADD']; - - //PreInit these values - $content['groupname'] = ""; - $content['groupdescription'] = ""; - } - else if ($_GET['op'] == "adduser" && isset($_GET['id']) ) - { - //PreInit these values - $content['GROUPID'] = intval( DB_RemoveBadChars($_GET['id']) ); - - // Set Mode to add - $content['ISADDUSER'] = "true"; - $content['GROUP_FORMACTION'] = "adduser"; - $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_ADDUSER']; - - // --- Get Groupname - $sqlquery = "SELECT " . - DB_GROUPS . ".groupname " . - " FROM " . DB_GROUPS . - " WHERE " . DB_GROUPS . ".id = " . $content['GROUPID']; - $result = DB_Query($sqlquery); - $tmparray = DB_GetSingleRow($result, true); - - if ( isset($tmparray) ) - { - // Copy Groupname - $content['GROUPNAME'] = $tmparray['groupname']; - - // --- Get Group Members - $sqlquery = "SELECT " . - DB_GROUPMEMBERS. ".userid " . - " FROM " . DB_GROUPMEMBERS . - " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPID']; - $result = DB_Query($sqlquery); - $tmparray = DB_GetAllRows($result, true); - if ( count($tmparray) > 0 ) - { - // Add UserID's to where clause! - foreach ($tmparray as $datarow) - { - if ( isset($whereclause) ) - $whereclause .= ", " . $datarow['userid']; - else - $whereclause = " WHERE " . DB_USERS . ".id NOT IN (" . $datarow['userid']; - } - // Finish whereclause - $whereclause .= ") "; - } - else - $whereclause = ""; - // --- - - // --- Create LIST of Users which are available for selection - $sqlquery = "SELECT " . - DB_USERS. ".ID as userid, " . - DB_USERS. ".username " . - " FROM " . DB_USERS . - " LEFT OUTER JOIN (" . DB_GROUPMEMBERS . - ") ON (" . - DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . - $whereclause . - " ORDER BY " . DB_USERS . ".username"; - $result = DB_Query($sqlquery); - $content['SUBUSERS'] = DB_GetAllRows($result, true); - - if ( count($content['SUBUSERS']) <= 0 ) - { - // Disable FORM: - $content['ISADDUSER'] = false; - - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERRORNOMOREUSERS'], $content['GROUPNAME'] ); - } - } - else - { - // Disable FORM: - $content['ISADDUSER'] = false; - - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); - } - // --- - } - else if ($_GET['op'] == "removeuser" && isset($_GET['id']) ) - { - //PreInit these values - $content['GROUPID'] = intval( DB_RemoveBadChars($_GET['id']) ); - - // Set Mode to add - $content['ISREMOVEUSER'] = "true"; - $content['GROUP_FORMACTION'] = "removeuser"; - $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_USERDELETE']; - - // --- Get Groupname - $sqlquery = "SELECT " . - DB_GROUPS . ".groupname " . - " FROM " . DB_GROUPS . - " WHERE " . DB_GROUPS . ".id = " . $content['GROUPID']; - $result = DB_Query($sqlquery); - $tmparray = DB_GetSingleRow($result, true); - - if ( isset($tmparray) ) - { - // Copy Groupname - $content['GROUPNAME'] = $tmparray['groupname']; - - // --- Get Group Members - $sqlquery = "SELECT " . - DB_GROUPMEMBERS. ".userid, " . - DB_USERS. ".username " . - " FROM " . DB_GROUPMEMBERS . - " INNER JOIN (" . DB_USERS . - ") ON (" . - DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . - " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPID']; - $result = DB_Query($sqlquery); - $content['SUBRMUSERS'] = DB_GetAllRows($result, true); - if ( count($content['SUBRMUSERS']) <= 0 ) - { - // Disable FORM: - $content['ISREMOVEUSER'] = false; - - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERRORNOUSERSINGROUP'], $content['GROUPNAME'] ); - } - } - else - { - // Disable FORM: - $content['ISREMOVEUSER'] = false; - - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); - } - - } - else if ($_GET['op'] == "edit") - { - // Set Mode to edit - $content['ISEDITORNEWGROUP'] = "true"; - $content['GROUP_FORMACTION'] = "editgroup"; - $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_EDIT']; - - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['GROUPID'] = DB_RemoveBadChars($_GET['id']); - - $sqlquery = "SELECT * " . - " FROM " . DB_GROUPS . - " WHERE ID = " . $content['GROUPID']; - - $result = DB_Query($sqlquery); - $myuser = DB_GetSingleRow($result, true); - if ( isset($myuser['groupname']) ) - { - $content['GROUPID'] = $myuser['ID']; - $content['groupname'] = $myuser['groupname']; - $content['groupdescription'] = $myuser['groupdescription']; - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; - } - } - else if ($_GET['op'] == "delete") - { - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['GROUPID'] = DB_RemoveBadChars($_GET['id']); - - // Get GroupInfo - $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID'] ); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['groupname']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - else - { - // --- Ask for deletion first! - if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) - { - // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_GROUP_WARNDELETEGROUP'], $myrow['groupname'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); - } - // --- - - // do the delete! - $result = DB_Query( "DELETE FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID'] ); - if ($result == FALSE) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_DELGROUP'], $content['USERID'] ); - } - else - DB_FreeQuery($result); - - // TODO: DELETE GROUP SETTINGS, GROUP MEMBERSHIP ... - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENDEL'], $myrow['groupname'] ) , "groups.php" ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; - } - } -} - -if ( isset($_POST['op']) ) -{ - if ( isset ($_POST['id']) ) { $content['GROUPID'] = intval( DB_RemoveBadChars($_POST['id']) ); } else {$content['GROUPID'] = ""; } - if ( isset ($_POST['groupname']) ) { $content['groupname'] = DB_RemoveBadChars($_POST['groupname']); } else {$content['groupname'] = ""; } - if ( isset ($_POST['groupdescription']) ) { $content['groupdescription'] = DB_RemoveBadChars($_POST['groupdescription']); } else {$content['groupdescription'] = ""; } - - // Check mandotary values - if ( $content['groupname'] == "" ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPEMPTY']; - } - - if ( !isset($content['ISERROR']) ) - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewgroup" ) - { - $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE groupname = '" . $content['groupname'] . "'"); - $myrow = DB_GetSingleRow($result, true); - if ( isset($myrow['groupname']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPNAMETAKEN']; - } - else - { - // Add new Group now! - $result = DB_Query("INSERT INTO " . DB_GROUPS . " (groupname, groupdescription) - VALUES ( '" . $content['groupname'] . "', - '" . $content['groupdescription'] . "' )"); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_HASBEENADDED'], $content['groupname'] ) , "groups.php" ); - } - } - else if ( $_POST['op'] == "editgroup" ) - { - $result = DB_Query("SELECT ID FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); - } - else - { - // Edit the User now! - $result = DB_Query("UPDATE " . DB_GROUPS . " SET - groupname = '" . $content['groupname'] . "', - groupdescription = '" . $content['groupdescription'] . "' - WHERE ID = " . $content['GROUPID']); - DB_FreeQuery($result); - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENEDIT'], $content['groupname']) , "groups.php" ); - } - } - else if ( $_POST['op'] == "adduser" ) - { - if ( isset($_POST['userid']) ) - { - // Copy UserID - $content['USERID'] = intval( DB_RemoveBadChars($_POST['userid']) ); - - $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE id = " . $content['USERID']); - $myrow = DB_GetSingleRow($result, true); - if ( isset($myrow['username']) ) - { - // Add Groupmembership now! - $result = DB_Query("INSERT INTO " . DB_GROUPMEMBERS . " (groupid, userid, is_member) - VALUES ( " . $content['GROUPID'] . ", - " . $content['USERID'] . ", - 1 )"); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_USERHASBEENADDEDGROUP'], $myrow['username'], $content['groupname'] ) , "groups.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_USERIDMISSING']; - } - } - else if ( $_POST['op'] == "removeuser" ) - { - if ( isset($_POST['userid']) ) - { - // Copy UserID - $content['USERID'] = intval( DB_RemoveBadChars($_POST['userid']) ); - - $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE id = " . $content['USERID']); - $myrow = DB_GetSingleRow($result, true); - if ( isset($myrow['username']) ) - { - // remove user from group - $result = DB_Query( "DELETE FROM " . DB_GROUPMEMBERS . " WHERE userid = " . $content['USERID'] . " AND groupid = " . $content['GROUPID']); - if ($result == FALSE) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_REMUSERFROMGROUP'], $myrow['username'], $content['groupname'] ); - } - else - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_USERHASBEENREMOVED'], $myrow['username'], $content['groupname'] ) , "groups.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_USERIDMISSING']; - } - } - } -} - -if ( !isset($_POST['op']) && !isset($_GET['op']) ) -{ - // Default Mode = List Groups - $content['LISTGROUPS'] = "true"; - - // Read all Groupentries - $sqlquery = "SELECT ID, " . - " groupname, " . - " groupdescription " . - " FROM " . DB_GROUPS. - " ORDER BY ID "; - $result = DB_Query($sqlquery); - $content['GROUPS'] = DB_GetAllRows($result, true); - - if ( count($content['GROUPS']) > 0 ) - { - // --- Process Groups - for($i = 0; $i < count($content['GROUPS']); $i++) - { - // --- Set CSS Class - if ( $i % 2 == 0 ) - $content['GROUPS'][$i]['cssclass'] = "line1"; - else - $content['GROUPS'][$i]['cssclass'] = "line2"; - // --- - - // --- Read all Memberentries for this group - $sqlquery = "SELECT " . - DB_USERS. ".username, " . - DB_GROUPMEMBERS . ".userid, " . - DB_GROUPMEMBERS . ".groupid, " . - DB_GROUPMEMBERS . ".is_member " . - " FROM " . DB_GROUPMEMBERS . - " INNER JOIN (" . DB_USERS . - ") ON (" . - DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . - " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPS'][$i]['ID'] . - " ORDER BY " . DB_USERS . ".username"; - $result = DB_Query($sqlquery); - $content['GROUPS'][$i]['USERS'] = DB_GetAllRows($result, true); - - if ( count($content['GROUPS'][$i]['USERS']) > 0 ) - { - // Enable Groupmembers - $content['GROUPS'][$i]['GROUPMEMBERS'] = true; - - // Process Groups - $subUserCount = count($content['GROUPS'][$i]['USERS']); - for($j = 0; $j < $subUserCount; $j++) - $content['GROUPS'][$i]['USERS'][$j]['seperator'] = ", "; - $content['GROUPS'][$i]['USERS'][$subUserCount-1]['seperator'] = ""; // last one is empty - } - // --- - } - // --- - } - else - $content['EMPTYGROUPS'] = "true"; -} -// --- END Custom Code - -// --- BEGIN CREATE TITLE -$content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_GROUPOPT']; -// --- END CREATE TITLE - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "admin/admin_groups.html"); -$page -> output(); -// --- - + Helps administrating groups + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +// Only if the user is an admin! +if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWGROUP'] = "true"; + $content['GROUP_FORMACTION'] = "addnewgroup"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_ADD']; + + //PreInit these values + $content['groupname'] = ""; + $content['groupdescription'] = ""; + } + else if ($_GET['op'] == "adduser" && isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = intval( DB_RemoveBadChars($_GET['id']) ); + + // Set Mode to add + $content['ISADDUSER'] = "true"; + $content['GROUP_FORMACTION'] = "adduser"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_ADDUSER']; + + // --- Get Groupname + $sqlquery = "SELECT " . + DB_GROUPS . ".groupname " . + " FROM " . DB_GROUPS . + " WHERE " . DB_GROUPS . ".id = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $tmparray = DB_GetSingleRow($result, true); + + if ( isset($tmparray) ) + { + // Copy Groupname + $content['GROUPNAME'] = $tmparray['groupname']; + + // --- Get Group Members + $sqlquery = "SELECT " . + DB_GROUPMEMBERS. ".userid " . + " FROM " . DB_GROUPMEMBERS . + " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $tmparray = DB_GetAllRows($result, true); + if ( count($tmparray) > 0 ) + { + // Add UserID's to where clause! + foreach ($tmparray as $datarow) + { + if ( isset($whereclause) ) + $whereclause .= ", " . $datarow['userid']; + else + $whereclause = " WHERE " . DB_USERS . ".id NOT IN (" . $datarow['userid']; + } + // Finish whereclause + $whereclause .= ") "; + } + else + $whereclause = ""; + // --- + + // --- Create LIST of Users which are available for selection + $sqlquery = "SELECT " . + DB_USERS. ".ID as userid, " . + DB_USERS. ".username " . + " FROM " . DB_USERS . + " LEFT OUTER JOIN (" . DB_GROUPMEMBERS . + ") ON (" . + DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . + $whereclause . + " ORDER BY " . DB_USERS . ".username"; + $result = DB_Query($sqlquery); + $content['SUBUSERS'] = DB_GetAllRows($result, true); + + if ( count($content['SUBUSERS']) <= 0 ) + { + // Disable FORM: + $content['ISADDUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERRORNOMOREUSERS'], $content['GROUPNAME'] ); + } + } + else + { + // Disable FORM: + $content['ISADDUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + // --- + } + else if ($_GET['op'] == "removeuser" && isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = intval( DB_RemoveBadChars($_GET['id']) ); + + // Set Mode to add + $content['ISREMOVEUSER'] = "true"; + $content['GROUP_FORMACTION'] = "removeuser"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_USERDELETE']; + + // --- Get Groupname + $sqlquery = "SELECT " . + DB_GROUPS . ".groupname " . + " FROM " . DB_GROUPS . + " WHERE " . DB_GROUPS . ".id = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $tmparray = DB_GetSingleRow($result, true); + + if ( isset($tmparray) ) + { + // Copy Groupname + $content['GROUPNAME'] = $tmparray['groupname']; + + // --- Get Group Members + $sqlquery = "SELECT " . + DB_GROUPMEMBERS. ".userid, " . + DB_USERS. ".username " . + " FROM " . DB_GROUPMEMBERS . + " INNER JOIN (" . DB_USERS . + ") ON (" . + DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . + " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPID']; + $result = DB_Query($sqlquery); + $content['SUBRMUSERS'] = DB_GetAllRows($result, true); + if ( count($content['SUBRMUSERS']) <= 0 ) + { + // Disable FORM: + $content['ISREMOVEUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERRORNOUSERSINGROUP'], $content['GROUPNAME'] ); + } + } + else + { + // Disable FORM: + $content['ISREMOVEUSER'] = false; + + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWGROUP'] = "true"; + $content['GROUP_FORMACTION'] = "editgroup"; + $content['GROUP_SENDBUTTON'] = $content['LN_GROUP_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT * " . + " FROM " . DB_GROUPS . + " WHERE ID = " . $content['GROUPID']; + + $result = DB_Query($sqlquery); + $myuser = DB_GetSingleRow($result, true); + if ( isset($myuser['groupname']) ) + { + $content['GROUPID'] = $myuser['ID']; + $content['groupname'] = $myuser['groupname']; + $content['groupdescription'] = $myuser['groupdescription']; + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['GROUPID'] = DB_RemoveBadChars($_GET['id']); + + // Get GroupInfo + $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['groupname']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + else + { + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_GROUP_WARNDELETEGROUP'], $myrow['groupname'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_DELGROUP'], $content['USERID'] ); + } + else + DB_FreeQuery($result); + + // TODO: DELETE GROUP SETTINGS, GROUP MEMBERSHIP ... + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENDEL'], $myrow['groupname'] ) , "groups.php" ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_INVALIDGROUP']; + } + } +} + +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['GROUPID'] = intval( DB_RemoveBadChars($_POST['id']) ); } else {$content['GROUPID'] = ""; } + if ( isset ($_POST['groupname']) ) { $content['groupname'] = DB_RemoveBadChars($_POST['groupname']); } else {$content['groupname'] = ""; } + if ( isset ($_POST['groupdescription']) ) { $content['groupdescription'] = DB_RemoveBadChars($_POST['groupdescription']); } else {$content['groupdescription'] = ""; } + + // Check mandotary values + if ( $content['groupname'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPEMPTY']; + } + + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewgroup" ) + { + $result = DB_Query("SELECT groupname FROM " . DB_GROUPS . " WHERE groupname = '" . $content['groupname'] . "'"); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['groupname']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_GROUPNAMETAKEN']; + } + else + { + // Add new Group now! + $result = DB_Query("INSERT INTO " . DB_GROUPS . " (groupname, groupdescription) + VALUES ( '" . $content['groupname'] . "', + '" . $content['groupdescription'] . "' )"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_HASBEENADDED'], $content['groupname'] ) , "groups.php" ); + } + } + else if ( $_POST['op'] == "editgroup" ) + { + $result = DB_Query("SELECT ID FROM " . DB_GROUPS . " WHERE ID = " . $content['GROUPID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_IDNOTFOUND'], $content['GROUPID'] ); + } + else + { + // Edit the User now! + $result = DB_Query("UPDATE " . DB_GROUPS . " SET + groupname = '" . $content['groupname'] . "', + groupdescription = '" . $content['groupdescription'] . "' + WHERE ID = " . $content['GROUPID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_ERROR_HASBEENEDIT'], $content['groupname']) , "groups.php" ); + } + } + else if ( $_POST['op'] == "adduser" ) + { + if ( isset($_POST['userid']) ) + { + // Copy UserID + $content['USERID'] = intval( DB_RemoveBadChars($_POST['userid']) ); + + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE id = " . $content['USERID']); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['username']) ) + { + // Add Groupmembership now! + $result = DB_Query("INSERT INTO " . DB_GROUPMEMBERS . " (groupid, userid, is_member) + VALUES ( " . $content['GROUPID'] . ", + " . $content['USERID'] . ", + 1 )"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_USERHASBEENADDEDGROUP'], $myrow['username'], $content['groupname'] ) , "groups.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_USERIDMISSING']; + } + } + else if ( $_POST['op'] == "removeuser" ) + { + if ( isset($_POST['userid']) ) + { + // Copy UserID + $content['USERID'] = intval( DB_RemoveBadChars($_POST['userid']) ); + + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE id = " . $content['USERID']); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['username']) ) + { + // remove user from group + $result = DB_Query( "DELETE FROM " . DB_GROUPMEMBERS . " WHERE userid = " . $content['USERID'] . " AND groupid = " . $content['GROUPID']); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_GROUP_ERROR_REMUSERFROMGROUP'], $myrow['username'], $content['groupname'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_GROUP_USERHASBEENREMOVED'], $myrow['username'], $content['groupname'] ) , "groups.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_GROUP_ERROR_USERIDMISSING']; + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Groups + $content['LISTGROUPS'] = "true"; + + // Read all Groupentries + $sqlquery = "SELECT ID, " . + " groupname, " . + " groupdescription " . + " FROM " . DB_GROUPS. + " ORDER BY ID "; + $result = DB_Query($sqlquery); + $content['GROUPS'] = DB_GetAllRows($result, true); + + if ( count($content['GROUPS']) > 0 ) + { + // --- Process Groups + for($i = 0; $i < count($content['GROUPS']); $i++) + { + // --- Set CSS Class + if ( $i % 2 == 0 ) + $content['GROUPS'][$i]['cssclass'] = "line1"; + else + $content['GROUPS'][$i]['cssclass'] = "line2"; + // --- + + // --- Read all Memberentries for this group + $sqlquery = "SELECT " . + DB_USERS. ".username, " . + DB_GROUPMEMBERS . ".userid, " . + DB_GROUPMEMBERS . ".groupid, " . + DB_GROUPMEMBERS . ".is_member " . + " FROM " . DB_GROUPMEMBERS . + " INNER JOIN (" . DB_USERS . + ") ON (" . + DB_GROUPMEMBERS . ".userid=" . DB_USERS . ".ID) " . + " WHERE " . DB_GROUPMEMBERS . ".groupid = " . $content['GROUPS'][$i]['ID'] . + " ORDER BY " . DB_USERS . ".username"; + $result = DB_Query($sqlquery); + $content['GROUPS'][$i]['USERS'] = DB_GetAllRows($result, true); + + if ( count($content['GROUPS'][$i]['USERS']) > 0 ) + { + // Enable Groupmembers + $content['GROUPS'][$i]['GROUPMEMBERS'] = true; + + // Process Groups + $subUserCount = count($content['GROUPS'][$i]['USERS']); + for($j = 0; $j < $subUserCount; $j++) + $content['GROUPS'][$i]['USERS'][$j]['seperator'] = ", "; + $content['GROUPS'][$i]['USERS'][$subUserCount-1]['seperator'] = ""; // last one is empty + } + // --- + } + // --- + } + else + $content['EMPTYGROUPS'] = "true"; +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_GROUPOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_groups.html"); +$page -> output(); +// --- + ?> \ No newline at end of file diff --git a/src/admin/result.php b/src/admin/result.php index aa894b6..adf271f 100644 --- a/src/admin/result.php +++ b/src/admin/result.php @@ -1,87 +1,87 @@ - Shows ... - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './../'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); - -// Include LogStream facility -// include($gl_root_path . 'classes/logstream.class.php'); - -// Set PAGE to be ADMINPAGE! -define('IS_ADMINPAGE', true); -$content['IS_ADMINPAGE'] = true; - -InitPhpLogCon(); -InitSourceConfigs(); -InitFrontEndDefaults(); // Only in WebFrontEnd -InitFilterHelpers(); // Helpers for frontend filtering! - -// Init admin langauge file now! -IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); - -// Hardcoded atm -$content['REDIRSECONDS'] = 2; -// *** *** // - -// --- CONTENT Vars -if ( isset($_GET['redir']) ) -{ - $content['EXTRA_METATAGS'] = ''; - $content['SZREDIR'] = urldecode($_GET['redir']); -} -else -{ - $_GET['redir'] = "index.php"; -} - -if ( isset($_GET['msg']) ) - $content['SZMSG'] = urldecode($_GET['msg']); -else - $content['SZMSG'] = $content["LN_ADMIN_UNKNOWNSTATE"]; - -$content['TITLE'] = "phpLogCon - Redirecting to '" . $content['SZREDIR'] . "' in 5 seconds"; // Title of the Page -// --- - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "admin/result.html"); -$page -> output(); -// --- - + Shows ... + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +// include($gl_root_path . 'classes/logstream.class.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); + +// Hardcoded atm +$content['REDIRSECONDS'] = 2; +// *** *** // + +// --- CONTENT Vars +if ( isset($_GET['redir']) ) +{ + $content['EXTRA_METATAGS'] = ''; + $content['SZREDIR'] = urldecode($_GET['redir']); +} +else +{ + $_GET['redir'] = "index.php"; +} + +if ( isset($_GET['msg']) ) + $content['SZMSG'] = urldecode($_GET['msg']); +else + $content['SZMSG'] = $content["LN_ADMIN_UNKNOWNSTATE"]; + +$content['TITLE'] = "phpLogCon - Redirecting to '" . $content['SZREDIR'] . "' in 5 seconds"; // Title of the Page +// --- + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/result.html"); +$page -> output(); +// --- + ?> \ No newline at end of file diff --git a/src/admin/searches.php b/src/admin/searches.php index 7e3128e..69cf644 100644 --- a/src/admin/searches.php +++ b/src/admin/searches.php @@ -1,330 +1,330 @@ - Helps administrating custom searches - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './../'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); - -// Set PAGE to be ADMINPAGE! -define('IS_ADMINPAGE', true); -$content['IS_ADMINPAGE'] = true; -InitPhpLogCon(); -InitSourceConfigs(); -InitFrontEndDefaults(); // Only in WebFrontEnd -InitFilterHelpers(); // Helpers for frontend filtering! - -// Init admin langauge file now! -IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); -// --- - -// --- BEGIN Custom Code -if ( isset($_GET['op']) ) -{ - if ($_GET['op'] == "add") - { - // Set Mode to add - $content['ISEDITORNEWSEARCH'] = "true"; - $content['SEARCH_FORMACTION'] = "addnewsearch"; - $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_ADD']; - - //PreInit these values - $content['DisplayName'] = ""; - $content['SearchQuery'] = ""; - $content['userid'] = null; - $content['CHECKED_ISUSERONLY'] = ""; - $content['SEARCHID'] = ""; - - // --- Check if groups are available - $content['SUBGROUPS'] = GetGroupsForSelectfield(); - if ( is_array($content['SUBGROUPS']) ) - $content['ISGROUPSAVAILABLE'] = true; - else - $content['ISGROUPSAVAILABLE'] = false; - } - else if ($_GET['op'] == "edit") - { - // Set Mode to edit - $content['ISEDITORNEWSEARCH'] = "true"; - $content['SEARCH_FORMACTION'] = "editsearch"; - $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_EDIT']; - - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); - - $sqlquery = "SELECT * " . - " FROM " . DB_SEARCHES . - " WHERE ID = " . $content['SEARCHID']; - - $result = DB_Query($sqlquery); - $mysearch = DB_GetSingleRow($result, true); - if ( isset($mysearch['DisplayName']) ) - { - $content['SEARCHID'] = $mysearch['ID']; - $content['DisplayName'] = $mysearch['DisplayName']; - $content['SearchQuery'] = $mysearch['SearchQuery']; - if ( $mysearch['userid'] != null ) - $content['CHECKED_ISUSERONLY'] = "checked"; - else - $content['CHECKED_ISUSERONLY'] = ""; - - // --- Check if groups are available - $content['SUBGROUPS'] = GetGroupsForSelectfield(); - if ( is_array($content['SUBGROUPS']) ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - { - if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) - $content['SUBGROUPS'][$i]['group_selected'] = "selected"; - else - $content['SUBGROUPS'][$i]['group_selected'] = ""; - } - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - } - else - $content['ISGROUPSAVAILABLE'] = false; - // --- - } - else - { - $content['ISEDITORNEWSEARCH'] = false; - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); - } - } - else - { - $content['ISEDITORNEWSEARCH'] = false; - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; - } - } - else if ($_GET['op'] == "delete") - { - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); - - // Get UserInfo - $result = DB_Query("SELECT DisplayName FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['DisplayName']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); - } - - // --- Ask for deletion first! - if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) - { - // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SEARCH_WARNDELETESEARCH'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); - } - // --- - - // do the delete! - $result = DB_Query( "DELETE FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); - if ($result == FALSE) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_DELSEARCH'], $content['SEARCHID'] ); - } - else - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "searches.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; - } - } -} - -if ( isset($_POST['op']) ) -{ - if ( isset ($_POST['id']) ) { $content['SEARCHID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SEARCHID'] = -1; } - if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } - if ( isset ($_POST['SearchQuery']) ) { $content['SearchQuery'] = DB_RemoveBadChars($_POST['SearchQuery']); } else {$content['SearchQuery'] = ""; } - - // User & Group handeled specially - if ( isset ($_POST['isuseronly']) ) - { - $content['userid'] = $content['SESSION_USERID']; - $content['groupid'] = "null"; // Either user or group not both! - } - else - { - $content['userid'] = "null"; - if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) - $content['groupid'] = intval($_POST['groupid']); - else - $content['groupid'] = "null"; - } - - // --- Check mandotary values - if ( $content['DisplayName'] == "" ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY']; - } - else if ( $content['SearchQuery'] == "" ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY']; - } - // --- - - if ( !isset($content['ISERROR']) ) - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewsearch" ) - { - // Add custom search now! - $sqlquery = "INSERT INTO " . DB_SEARCHES . " (DisplayName, SearchQuery, userid, groupid) - VALUES ('" . $content['DisplayName'] . "', - '" . $content['SearchQuery'] . "', - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; - $result = DB_Query($sqlquery); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENADDED'], $content['DisplayName'] ) , "searches.php" ); - } - else if ( $_POST['op'] == "editsearch" ) - { - $result = DB_Query("SELECT ID FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); - } - else - { - // Edit the Search Entry now! - $result = DB_Query("UPDATE " . DB_SEARCHES . " SET - DisplayName = '" . $content['DisplayName'] . "', - SearchQuery = '" . $content['SearchQuery'] . "', - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['SEARCHID']); - DB_FreeQuery($result); - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENEDIT'], $content['DisplayName']) , "searches.php" ); - } - } - } -} - -if ( !isset($_POST['op']) && !isset($_GET['op']) ) -{ - // Default Mode = List Searches - $content['LISTSEARCHES'] = "true"; - - // Copy Search array for further modifications - $content['SEARCHES'] = $content['Search']; - - $i = 0; // Help counter! - foreach ($content['SEARCHES'] as &$mySearch ) - { - $mySearch['SearchQuery_Display'] = strlen($mySearch['SearchQuery']) > 25 ? substr($mySearch['SearchQuery'], 0, 25) . " ..." : $mySearch['SearchQuery']; - - // Allow EDIT - $mySearch['ActionsAllowed'] = true; - - // --- Set Image for Type - if ( $mySearch['userid'] != null ) - { - $mySearch['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; - $mySearch['SearchTypeText'] = $content["LN_GEN_USERONLY"]; - } - else if ( $mySearch['groupid'] != null ) - { - $mySearch['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; - $mySearch['SearchTypeText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $mySearch['groupname'] ); - - // Check if is ADMIN User, deny if normal user! - if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - $mySearch['ActionsAllowed'] = false; - } - else - { - $mySearch['SearchTypeImage'] = $content["MENU_GLOBAL"]; - $mySearch['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; - - // Check if is ADMIN User, deny if normal user! - if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - $mySearch['ActionsAllowed'] = false; - } - // --- - - // --- Set CSS Class - if ( $i % 2 == 0 ) - $mySearch['cssclass'] = "line1"; - else - $mySearch['cssclass'] = "line2"; - $i++; - // --- - } - // --- -} -// --- END Custom Code - -// --- BEGIN CREATE TITLE -$content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_SEARCHOPT']; -// --- END CREATE TITLE - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "admin/admin_searches.html"); -$page -> output(); -// --- - + Helps administrating custom searches + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWSEARCH'] = "true"; + $content['SEARCH_FORMACTION'] = "addnewsearch"; + $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_ADD']; + + //PreInit these values + $content['DisplayName'] = ""; + $content['SearchQuery'] = ""; + $content['userid'] = null; + $content['CHECKED_ISUSERONLY'] = ""; + $content['SEARCHID'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + $content['ISGROUPSAVAILABLE'] = true; + else + $content['ISGROUPSAVAILABLE'] = false; + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWSEARCH'] = "true"; + $content['SEARCH_FORMACTION'] = "editsearch"; + $content['SEARCH_SENDBUTTON'] = $content['LN_SEARCH_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT * " . + " FROM " . DB_SEARCHES . + " WHERE ID = " . $content['SEARCHID']; + + $result = DB_Query($sqlquery); + $mysearch = DB_GetSingleRow($result, true); + if ( isset($mysearch['DisplayName']) ) + { + $content['SEARCHID'] = $mysearch['ID']; + $content['DisplayName'] = $mysearch['DisplayName']; + $content['SearchQuery'] = $mysearch['SearchQuery']; + if ( $mysearch['userid'] != null ) + $content['CHECKED_ISUSERONLY'] = "checked"; + else + $content['CHECKED_ISUSERONLY'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $mysearch['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysearch['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else + { + $content['ISEDITORNEWSEARCH'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + } + else + { + $content['ISEDITORNEWSEARCH'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SEARCHID'] = DB_RemoveBadChars($_GET['id']); + + // Get UserInfo + $result = DB_Query("SELECT DisplayName FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['DisplayName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SEARCH_WARNDELETESEARCH'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_DELSEARCH'], $content['SEARCHID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "searches.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + } + } +} + +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['SEARCHID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SEARCHID'] = -1; } + if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } + if ( isset ($_POST['SearchQuery']) ) { $content['SearchQuery'] = DB_RemoveBadChars($_POST['SearchQuery']); } else {$content['SearchQuery'] = ""; } + + // User & Group handeled specially + if ( isset ($_POST['isuseronly']) ) + { + $content['userid'] = $content['SESSION_USERID']; + $content['groupid'] = "null"; // Either user or group not both! + } + else + { + $content['userid'] = "null"; + if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) + $content['groupid'] = intval($_POST['groupid']); + else + $content['groupid'] = "null"; + } + + // --- Check mandotary values + if ( $content['DisplayName'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY']; + } + else if ( $content['SearchQuery'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY']; + } + // --- + + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewsearch" ) + { + // Add custom search now! + $sqlquery = "INSERT INTO " . DB_SEARCHES . " (DisplayName, SearchQuery, userid, groupid) + VALUES ('" . $content['DisplayName'] . "', + '" . $content['SearchQuery'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENADDED'], $content['DisplayName'] ) , "searches.php" ); + } + else if ( $_POST['op'] == "editsearch" ) + { + $result = DB_Query("SELECT ID FROM " . DB_SEARCHES . " WHERE ID = " . $content['SEARCHID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SEARCH_ERROR_IDNOTFOUND'], $content['SEARCHID'] ); + } + else + { + // Edit the Search Entry now! + $result = DB_Query("UPDATE " . DB_SEARCHES . " SET + DisplayName = '" . $content['DisplayName'] . "', + SearchQuery = '" . $content['SearchQuery'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SEARCHID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_SEARCH_HASBEENEDIT'], $content['DisplayName']) , "searches.php" ); + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Searches + $content['LISTSEARCHES'] = "true"; + + // Copy Search array for further modifications + $content['SEARCHES'] = $content['Search']; + + $i = 0; // Help counter! + foreach ($content['SEARCHES'] as &$mySearch ) + { + $mySearch['SearchQuery_Display'] = strlen($mySearch['SearchQuery']) > 25 ? substr($mySearch['SearchQuery'], 0, 25) . " ..." : $mySearch['SearchQuery']; + + // Allow EDIT + $mySearch['ActionsAllowed'] = true; + + // --- Set Image for Type + if ( $mySearch['userid'] != null ) + { + $mySearch['SearchTypeImage'] = $content["MENU_ADMINUSERS"]; + $mySearch['SearchTypeText'] = $content["LN_GEN_USERONLY"]; + } + else if ( $mySearch['groupid'] != null ) + { + $mySearch['SearchTypeImage'] = $content["MENU_ADMINGROUPS"]; + $mySearch['SearchTypeText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $mySearch['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySearch['ActionsAllowed'] = false; + } + else + { + $mySearch['SearchTypeImage'] = $content["MENU_GLOBAL"]; + $mySearch['SearchTypeText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySearch['ActionsAllowed'] = false; + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $mySearch['cssclass'] = "line1"; + else + $mySearch['cssclass'] = "line2"; + $i++; + // --- + } + // --- +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_SEARCHOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_searches.html"); +$page -> output(); +// --- + ?> \ No newline at end of file diff --git a/src/admin/sources.php b/src/admin/sources.php index 656fbea..125ae6d 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -1,576 +1,576 @@ - Helps administrating phplogcon datasources - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './../'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); - -// Set PAGE to be ADMINPAGE! -define('IS_ADMINPAGE', true); -$content['IS_ADMINPAGE'] = true; -InitPhpLogCon(); -InitSourceConfigs(); -InitFrontEndDefaults(); // Only in WebFrontEnd -InitFilterHelpers(); // Helpers for frontend filtering! - -// Init admin langauge file now! -IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); -// --- - -// --- BEGIN Custom Code - -if ( isset($_GET['op']) ) -{ - if ($_GET['op'] == "add") - { - // Set Mode to add - $content['ISEDITORNEWSOURCE'] = "true"; - $content['SOURCE_FORMACTION'] = "addnewsource"; - $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_ADD']; - - //PreInit these values - $content['Name'] = ""; - $content['SourceType'] = SOURCE_DISK; - CreateSourceTypesList($content['SourceType']); - - // Init View List! - $content['SourceViewID'] = 'SYSLOG'; - $content['VIEWS'] = $content['Views']; - foreach ( $content['VIEWS'] as $myView ) - { - if ( $myView['ID'] == $content['SourceViewID'] ) - $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; - else - $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; - } - - // SOURCE_DISK specific - $content['SourceLogLineType'] = ""; - CreateLogLineTypesList($content['SourceLogLineType']); - $content['SourceDiskFile'] = "/var/log/syslog"; - - // SOURCE_DB specific - $content['SourceDBType'] = DB_MYSQL; - CreateDBTypesList($content['SourceDBType']); - $content['SourceDBName'] = "phplogcon"; - $content['SourceDBTableType'] = "monitorware"; - $content['SourceDBServer'] = "localhost"; - $content['SourceDBTableName'] = "systemevents"; - $content['SourceDBUser'] = "user"; - $content['SourceDBPassword'] = ""; - $content['SourceDBEnableRowCounting'] = "false"; - $content['SourceDBEnableRowCounting_true'] = ""; - $content['SourceDBEnableRowCounting_false'] = "checked"; - - // General stuff - $content['userid'] = null; - $content['CHECKED_ISUSERONLY'] = ""; - $content['SOURCEID'] = ""; - - // --- Check if groups are available - $content['SUBGROUPS'] = GetGroupsForSelectfield(); - if ( is_array($content['SUBGROUPS']) ) - $content['ISGROUPSAVAILABLE'] = true; - else - $content['ISGROUPSAVAILABLE'] = false; - } - else if ($_GET['op'] == "edit") - { - // Set Mode to edit - $content['ISEDITORNEWSOURCE'] = "true"; - $content['SOURCE_FORMACTION'] = "editsource"; - $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_EDIT']; - - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); - - // Check if exists - if ( is_numeric($content['SOURCEID']) && isset($content['Sources'][ $content['SOURCEID'] ]) ) - { - // Get Source reference - $mysource = $content['Sources'][ $content['SOURCEID'] ]; - - // Copy basic properties - $content['Name'] = $mysource['Name']; - $content['SourceType'] = $mysource['SourceType']; - CreateSourceTypesList($content['SourceType']); - - // Init View List! - $content['SourceViewID'] = $mysource['ViewID']; - $content['VIEWS'] = $content['Views']; - foreach ( $content['VIEWS'] as $myView ) - { - if ( $myView['ID'] == $content['SourceViewID'] ) - $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; - else - $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; - } - - // SOURCE_DISK specific - $content['SourceLogLineType'] = $mysource['LogLineType']; - CreateLogLineTypesList($content['SourceLogLineType']); - $content['SourceDiskFile'] = $mysource['DiskFile']; - - // SOURCE_DB specific - $content['SourceDBType'] = $mysource['DBType']; - CreateDBTypesList($content['SourceDBType']); - $content['SourceDBName'] = $mysource['DBName']; - $content['SourceDBTableType'] = $mysource['DBTableType']; - $content['SourceDBServer'] = $mysource['DBServer']; - $content['SourceDBTableName'] = $mysource['DBTableName']; - $content['SourceDBUser'] = $mysource['DBUser']; - $content['SourceDBPassword'] = $mysource['DBPassword']; - $content['SourceDBEnableRowCounting'] = $mysource['DBEnableRowCounting']; - if ( $content['SourceDBEnableRowCounting'] == 1 ) - { - $content['SourceDBEnableRowCounting_true'] = "checked"; - $content['SourceDBEnableRowCounting_false'] = ""; - } - else - { - $content['SourceDBEnableRowCounting_true'] = ""; - $content['SourceDBEnableRowCounting_false'] = "checked"; - } - - if ( $mysource['userid'] != null ) - $content['CHECKED_ISUSERONLY'] = "checked"; - else - $content['CHECKED_ISUSERONLY'] = ""; - - // --- Check if groups are available - $content['SUBGROUPS'] = GetGroupsForSelectfield(); - if ( is_array($content['SUBGROUPS']) ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - { - if ( $mysource['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysource['groupid'] ) - $content['SUBGROUPS'][$i]['group_selected'] = "selected"; - else - $content['SUBGROUPS'][$i]['group_selected'] = ""; - } - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - } - else - $content['ISGROUPSAVAILABLE'] = false; - // --- - } - else - { - $content['ISEDITORNEWSOURCE'] = false; - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; - } - } - else - { - $content['ISEDITORNEWSEARCH'] = false; - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; - } - } - else if ($_GET['op'] == "delete") - { - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); - - // Get UserInfo - $result = DB_Query("SELECT Name FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['Name']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); - } - - // --- Ask for deletion first! - if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) - { - // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SOURCES_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); - } - // --- - - // do the delete! - $result = DB_Query( "DELETE FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); - if ($result == FALSE) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_DELSOURCE'], $content['SOURCEID'] ); - } - else - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_HASBEENDEL'], $myrow['Name'] ) , "sources.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; - } - } -} - -if ( isset($_POST['op']) ) -{ - // Read parameters first! - if ( isset($_POST['id']) ) { $content['SOURCEID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SOURCEID'] = -1; } - if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } - if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } - if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } - - if ( isset($content['SourceType']) ) - { - // Disk Params - if ( $content['SourceType'] == SOURCE_DISK ) - { - if ( isset($_POST['SourceLogLineType']) ) { $content['SourceLogLineType'] = DB_RemoveBadChars($_POST['SourceLogLineType']); } - if ( isset($_POST['SourceDiskFile']) ) { $content['SourceDiskFile'] = DB_RemoveBadChars($_POST['SourceDiskFile']); } - } - // DB Params - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - if ( isset($_POST['SourceDBType']) ) { $content['SourceDBType'] = DB_RemoveBadChars($_POST['SourceDBType']); } - if ( isset($_POST['SourceDBName']) ) { $content['SourceDBName'] = DB_RemoveBadChars($_POST['SourceDBName']); } - if ( isset($_POST['SourceDBTableType']) ) { $content['SourceDBTableType'] = DB_RemoveBadChars($_POST['SourceDBTableType']); } - if ( isset($_POST['SourceDBServer']) ) { $content['SourceDBServer'] = DB_RemoveBadChars($_POST['SourceDBServer']); } - if ( isset($_POST['SourceDBTableName']) ) { $content['SourceDBTableName'] = DB_RemoveBadChars($_POST['SourceDBTableName']); } - if ( isset($_POST['SourceDBUser']) ) { $content['SourceDBUser'] = DB_RemoveBadChars($_POST['SourceDBUser']); } - if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } - if ( isset($_POST['SourceDBEnableRowCounting']) ) { $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceDBEnableRowCounting']); } - // Extra Check for this property - if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) - $_SESSION['SourceDBEnableRowCounting'] = "false"; - - } - } - - // User & Group handeled specially - if ( isset ($_POST['isuseronly']) ) - { - $content['userid'] = $content['SESSION_USERID']; - $content['groupid'] = "null"; // Either user or group not both! - } - else - { - $content['userid'] = "null"; - if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) - $content['groupid'] = intval($_POST['groupid']); - else - $content['groupid'] = "null"; - } - - // --- Check mandotary values - if ( $content['Name'] == "" ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_NAMEOFTHESOURCE'] ); - } - else if ( !isset($content['SourceType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SOURCETYPE'] ); - } - else if ( !isset($content['SourceViewID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_VIEW'] ); - } - else - { - // Disk Params - if ( $content['SourceType'] == SOURCE_DISK ) - { - if ( !isset($content['SourceLogLineType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_LOGLINETYPE'] ); - } - else if ( !isset($content['SourceDiskFile']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SYSLOGFILE'] ); - } - // Check if file is accessable! - else if ( !is_file($content['SourceDiskFile']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $content['SourceDiskFile'] ); - } - } - // DB Params - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - if ( !isset($content['SourceDBType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DATABASETYPEOPTIONS'] ); - } - else if ( !isset($content['SourceDBName']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBNAME'] ); - } - else if ( !isset($content['SourceDBTableType']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLETYPE'] ); - } - else if ( !isset($content['SourceDBServer']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBSERVER'] ); - } - else if ( !isset($content['SourceDBTableName']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLENAME'] ); - } - else if ( !isset($content['SourceDBUser']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBUSER'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'], $content['SourceDBType'] ); - } - } - - // --- Now ADD/EDIT do the processing! - if ( !isset($content['ISERROR']) ) - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewsource" ) - { - // Add custom search now! - if ( $content['SourceType'] == SOURCE_DISK ) - { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, LogLineType, DiskFile, userid, groupid) - VALUES ('" . $content['Name'] . "', - " . $content['SourceType'] . ", - '" . $content['SourceViewID'] . "', - '" . $content['SourceLogLineType'] . "', - '" . $content['SourceDiskFile'] . "', - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; - } - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) - VALUES ('" . $content['Name'] . "', - " . $content['SourceType'] . ", - '" . $content['SourceViewID'] . "', - '" . $content['SourceDBTableType'] . "', - " . $content['SourceDBType'] . ", - '" . $content['SourceDBServer'] . "', - '" . $content['SourceDBName'] . "', - '" . $content['SourceDBUser'] . "', - '" . $content['SourceDBPassword'] . "', - '" . $content['SourceDBTableName'] . "', - " . $content['SourceDBEnableRowCounting'] . ", - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; - } - - $result = DB_Query($sqlquery); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCE_HASBEENADDED'], $content['Name'] ) , "sources.php" ); - } - else if ( $_POST['op'] == "editsource" ) - { - $result = DB_Query("SELECT ID FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); - } - else - { - // Edit the Search Entry now! - if ( $content['SourceType'] == SOURCE_DISK ) - { - $sqlquery = "UPDATE " . DB_SOURCES . " SET - Name = '" . $content['Name'] . "', - SourceType = " . $content['SourceType'] . ", - ViewID = '" . $content['SourceViewID'] . "', - LogLineType = '" . $content['SourceLogLineType'] . "', - DiskFile = '" . $content['SourceDiskFile'] . "', - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['SOURCEID']; - } - else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) - { - $sqlquery = "UPDATE " . DB_SOURCES . " SET - Name = '" . $content['Name'] . "', - SourceType = " . $content['SourceType'] . ", - ViewID = '" . $content['SourceViewID'] . "', - DBTableType = '" . $content['SourceDBTableType'] . "', - DBType = " . $content['SourceDBType'] . ", - DBServer = '" . $content['SourceDBServer'] . "', - DBName = '" . $content['SourceDBName'] . "', - DBUser = '" . $content['SourceDBUser'] . "', - DBPassword = '" . $content['SourceDBPassword'] . "', - DBTableName = '" . $content['SourceDBTableName'] . "', - DBEnableRowCounting = " . $content['SourceDBEnableRowCounting'] . ", - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['SOURCEID']; - } - - $result = DB_Query($sqlquery); - DB_FreeQuery($result); - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_HASBEENEDIT'], $content['Name']) , "sources.php" ); - } - } - } -} - -if ( !isset($_POST['op']) && !isset($_GET['op']) ) -{ - // Default Mode = List Searches - $content['LISTSOURCES'] = "true"; - - // Copy Sources array for further modifications - $content['SOURCES'] = $content['Sources']; - - // --- Process Sources - $i = 0; // Help counter! - foreach ($content['SOURCES'] as &$mySource ) - { - // --- Set Image for Type - // NonNUMERIC are config files Sources, can not be editied - if ( is_numeric($mySource['ID']) ) - { - // Allow EDIT - $mySource['ActionsAllowed'] = true; - - if ( $mySource['userid'] != null ) - { - $mySource['SourcesAssignedToImage'] = $content["MENU_ADMINUSERS"]; - $mySource['SourcesAssignedToText'] = $content["LN_GEN_USERONLY"]; - } - else if ( $mySource['groupid'] != null ) - { - $mySource['SourcesAssignedToImage'] = $content["MENU_ADMINGROUPS"]; - $mySource['SourcesAssignedToText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $mySource['groupname'] ); - - // Check if is ADMIN User, deny if normal user! - if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - $mySource['ActionsAllowed'] = false; - } - else - { - $mySource['SourcesAssignedToImage'] = $content["MENU_GLOBAL"]; - $mySource['SourcesAssignedToText'] = $content["LN_GEN_GLOBAL"]; - - // Check if is ADMIN User, deny if normal user! - if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - $mySource['ActionsAllowed'] = false; - } - } - else - { - // Disallow EDIT - $mySource['ActionsAllowed'] = false; - - $mySource['SourcesAssignedToImage'] = $content["MENU_INTERNAL"]; - $mySource['SourcesAssignedToText'] = $content["LN_GEN_CONFIGFILE"]; - } - // --- - - // --- Set SourceType - if ( $mySource['SourceType'] == SOURCE_DISK ) - { - $mySource['SourcesTypeImage'] = $content["MENU_SOURCE_DISK"]; - $mySource['SourcesTypeText'] = $content["LN_SOURCES_DISK"]; - } - else if ( $mySource['SourceType'] == SOURCE_DB ) - { - $mySource['SourcesTypeImage'] = $content["MENU_SOURCE_DB"]; - $mySource['SourcesTypeText'] = $content["LN_SOURCES_DB"]; - } - else if ( $mySource['SourceType'] == SOURCE_PDO ) - { - $mySource['SourcesTypeImage'] = $content["MENU_SOURCE_PDO"]; - $mySource['SourcesTypeText'] = $content["LN_SOURCES_PDO"]; - } - // --- - - // --- Set CSS Class - if ( $i % 2 == 0 ) - $mySource['cssclass'] = "line1"; - else - $mySource['cssclass'] = "line2"; - $i++; - // --- - } - // --- -// print_r ( $content['SOURCES'] ); -} -// --- END Custom Code - -// --- BEGIN CREATE TITLE -$content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_SOURCEOPT']; -// --- END CREATE TITLE - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "admin/admin_sources.html"); -$page -> output(); -// --- - + Helps administrating phplogcon datasources + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWSOURCE'] = "true"; + $content['SOURCE_FORMACTION'] = "addnewsource"; + $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_ADD']; + + //PreInit these values + $content['Name'] = ""; + $content['SourceType'] = SOURCE_DISK; + CreateSourceTypesList($content['SourceType']); + + // Init View List! + $content['SourceViewID'] = 'SYSLOG'; + $content['VIEWS'] = $content['Views']; + foreach ( $content['VIEWS'] as $myView ) + { + if ( $myView['ID'] == $content['SourceViewID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + } + + // SOURCE_DISK specific + $content['SourceLogLineType'] = ""; + CreateLogLineTypesList($content['SourceLogLineType']); + $content['SourceDiskFile'] = "/var/log/syslog"; + + // SOURCE_DB specific + $content['SourceDBType'] = DB_MYSQL; + CreateDBTypesList($content['SourceDBType']); + $content['SourceDBName'] = "phplogcon"; + $content['SourceDBTableType'] = "monitorware"; + $content['SourceDBServer'] = "localhost"; + $content['SourceDBTableName'] = "systemevents"; + $content['SourceDBUser'] = "user"; + $content['SourceDBPassword'] = ""; + $content['SourceDBEnableRowCounting'] = "false"; + $content['SourceDBEnableRowCounting_true'] = ""; + $content['SourceDBEnableRowCounting_false'] = "checked"; + + // General stuff + $content['userid'] = null; + $content['CHECKED_ISUSERONLY'] = ""; + $content['SOURCEID'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + $content['ISGROUPSAVAILABLE'] = true; + else + $content['ISGROUPSAVAILABLE'] = false; + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWSOURCE'] = "true"; + $content['SOURCE_FORMACTION'] = "editsource"; + $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); + + // Check if exists + if ( is_numeric($content['SOURCEID']) && isset($content['Sources'][ $content['SOURCEID'] ]) ) + { + // Get Source reference + $mysource = $content['Sources'][ $content['SOURCEID'] ]; + + // Copy basic properties + $content['Name'] = $mysource['Name']; + $content['SourceType'] = $mysource['SourceType']; + CreateSourceTypesList($content['SourceType']); + + // Init View List! + $content['SourceViewID'] = $mysource['ViewID']; + $content['VIEWS'] = $content['Views']; + foreach ( $content['VIEWS'] as $myView ) + { + if ( $myView['ID'] == $content['SourceViewID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + } + + // SOURCE_DISK specific + $content['SourceLogLineType'] = $mysource['LogLineType']; + CreateLogLineTypesList($content['SourceLogLineType']); + $content['SourceDiskFile'] = $mysource['DiskFile']; + + // SOURCE_DB specific + $content['SourceDBType'] = $mysource['DBType']; + CreateDBTypesList($content['SourceDBType']); + $content['SourceDBName'] = $mysource['DBName']; + $content['SourceDBTableType'] = $mysource['DBTableType']; + $content['SourceDBServer'] = $mysource['DBServer']; + $content['SourceDBTableName'] = $mysource['DBTableName']; + $content['SourceDBUser'] = $mysource['DBUser']; + $content['SourceDBPassword'] = $mysource['DBPassword']; + $content['SourceDBEnableRowCounting'] = $mysource['DBEnableRowCounting']; + if ( $content['SourceDBEnableRowCounting'] == 1 ) + { + $content['SourceDBEnableRowCounting_true'] = "checked"; + $content['SourceDBEnableRowCounting_false'] = ""; + } + else + { + $content['SourceDBEnableRowCounting_true'] = ""; + $content['SourceDBEnableRowCounting_false'] = "checked"; + } + + if ( $mysource['userid'] != null ) + $content['CHECKED_ISUSERONLY'] = "checked"; + else + $content['CHECKED_ISUSERONLY'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $mysource['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysource['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else + { + $content['ISEDITORNEWSOURCE'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; + } + } + else + { + $content['ISEDITORNEWSEARCH'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); + + // Get UserInfo + $result = DB_Query("SELECT Name FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['Name']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); + } + + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SOURCES_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_DELSOURCE'], $content['SOURCEID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_HASBEENDEL'], $myrow['Name'] ) , "sources.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; + } + } +} + +if ( isset($_POST['op']) ) +{ + // Read parameters first! + if ( isset($_POST['id']) ) { $content['SOURCEID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SOURCEID'] = -1; } + if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } + if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } + if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } + + if ( isset($content['SourceType']) ) + { + // Disk Params + if ( $content['SourceType'] == SOURCE_DISK ) + { + if ( isset($_POST['SourceLogLineType']) ) { $content['SourceLogLineType'] = DB_RemoveBadChars($_POST['SourceLogLineType']); } + if ( isset($_POST['SourceDiskFile']) ) { $content['SourceDiskFile'] = DB_RemoveBadChars($_POST['SourceDiskFile']); } + } + // DB Params + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + if ( isset($_POST['SourceDBType']) ) { $content['SourceDBType'] = DB_RemoveBadChars($_POST['SourceDBType']); } + if ( isset($_POST['SourceDBName']) ) { $content['SourceDBName'] = DB_RemoveBadChars($_POST['SourceDBName']); } + if ( isset($_POST['SourceDBTableType']) ) { $content['SourceDBTableType'] = DB_RemoveBadChars($_POST['SourceDBTableType']); } + if ( isset($_POST['SourceDBServer']) ) { $content['SourceDBServer'] = DB_RemoveBadChars($_POST['SourceDBServer']); } + if ( isset($_POST['SourceDBTableName']) ) { $content['SourceDBTableName'] = DB_RemoveBadChars($_POST['SourceDBTableName']); } + if ( isset($_POST['SourceDBUser']) ) { $content['SourceDBUser'] = DB_RemoveBadChars($_POST['SourceDBUser']); } + if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } + if ( isset($_POST['SourceDBEnableRowCounting']) ) { $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceDBEnableRowCounting']); } + // Extra Check for this property + if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) + $_SESSION['SourceDBEnableRowCounting'] = "false"; + + } + } + + // User & Group handeled specially + if ( isset ($_POST['isuseronly']) ) + { + $content['userid'] = $content['SESSION_USERID']; + $content['groupid'] = "null"; // Either user or group not both! + } + else + { + $content['userid'] = "null"; + if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) + $content['groupid'] = intval($_POST['groupid']); + else + $content['groupid'] = "null"; + } + + // --- Check mandotary values + if ( $content['Name'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_NAMEOFTHESOURCE'] ); + } + else if ( !isset($content['SourceType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SOURCETYPE'] ); + } + else if ( !isset($content['SourceViewID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_VIEW'] ); + } + else + { + // Disk Params + if ( $content['SourceType'] == SOURCE_DISK ) + { + if ( !isset($content['SourceLogLineType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_LOGLINETYPE'] ); + } + else if ( !isset($content['SourceDiskFile']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SYSLOGFILE'] ); + } + // Check if file is accessable! + else if ( !is_file($content['SourceDiskFile']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $content['SourceDiskFile'] ); + } + } + // DB Params + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + if ( !isset($content['SourceDBType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DATABASETYPEOPTIONS'] ); + } + else if ( !isset($content['SourceDBName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBNAME'] ); + } + else if ( !isset($content['SourceDBTableType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLETYPE'] ); + } + else if ( !isset($content['SourceDBServer']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBSERVER'] ); + } + else if ( !isset($content['SourceDBTableName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLENAME'] ); + } + else if ( !isset($content['SourceDBUser']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBUSER'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'], $content['SourceDBType'] ); + } + } + + // --- Now ADD/EDIT do the processing! + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewsource" ) + { + // Add custom search now! + if ( $content['SourceType'] == SOURCE_DISK ) + { + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, LogLineType, DiskFile, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['SourceType'] . ", + '" . $content['SourceViewID'] . "', + '" . $content['SourceLogLineType'] . "', + '" . $content['SourceDiskFile'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + } + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['SourceType'] . ", + '" . $content['SourceViewID'] . "', + '" . $content['SourceDBTableType'] . "', + " . $content['SourceDBType'] . ", + '" . $content['SourceDBServer'] . "', + '" . $content['SourceDBName'] . "', + '" . $content['SourceDBUser'] . "', + '" . $content['SourceDBPassword'] . "', + '" . $content['SourceDBTableName'] . "', + " . $content['SourceDBEnableRowCounting'] . ", + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + } + + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCE_HASBEENADDED'], $content['Name'] ) , "sources.php" ); + } + else if ( $_POST['op'] == "editsource" ) + { + $result = DB_Query("SELECT ID FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); + } + else + { + // Edit the Search Entry now! + if ( $content['SourceType'] == SOURCE_DISK ) + { + $sqlquery = "UPDATE " . DB_SOURCES . " SET + Name = '" . $content['Name'] . "', + SourceType = " . $content['SourceType'] . ", + ViewID = '" . $content['SourceViewID'] . "', + LogLineType = '" . $content['SourceLogLineType'] . "', + DiskFile = '" . $content['SourceDiskFile'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SOURCEID']; + } + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + $sqlquery = "UPDATE " . DB_SOURCES . " SET + Name = '" . $content['Name'] . "', + SourceType = " . $content['SourceType'] . ", + ViewID = '" . $content['SourceViewID'] . "', + DBTableType = '" . $content['SourceDBTableType'] . "', + DBType = " . $content['SourceDBType'] . ", + DBServer = '" . $content['SourceDBServer'] . "', + DBName = '" . $content['SourceDBName'] . "', + DBUser = '" . $content['SourceDBUser'] . "', + DBPassword = '" . $content['SourceDBPassword'] . "', + DBTableName = '" . $content['SourceDBTableName'] . "', + DBEnableRowCounting = " . $content['SourceDBEnableRowCounting'] . ", + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SOURCEID']; + } + + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_HASBEENEDIT'], $content['Name']) , "sources.php" ); + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Searches + $content['LISTSOURCES'] = "true"; + + // Copy Sources array for further modifications + $content['SOURCES'] = $content['Sources']; + + // --- Process Sources + $i = 0; // Help counter! + foreach ($content['SOURCES'] as &$mySource ) + { + // --- Set Image for Type + // NonNUMERIC are config files Sources, can not be editied + if ( is_numeric($mySource['ID']) ) + { + // Allow EDIT + $mySource['ActionsAllowed'] = true; + + if ( $mySource['userid'] != null ) + { + $mySource['SourcesAssignedToImage'] = $content["MENU_ADMINUSERS"]; + $mySource['SourcesAssignedToText'] = $content["LN_GEN_USERONLY"]; + } + else if ( $mySource['groupid'] != null ) + { + $mySource['SourcesAssignedToImage'] = $content["MENU_ADMINGROUPS"]; + $mySource['SourcesAssignedToText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $mySource['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySource['ActionsAllowed'] = false; + } + else + { + $mySource['SourcesAssignedToImage'] = $content["MENU_GLOBAL"]; + $mySource['SourcesAssignedToText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $mySource['ActionsAllowed'] = false; + } + } + else + { + // Disallow EDIT + $mySource['ActionsAllowed'] = false; + + $mySource['SourcesAssignedToImage'] = $content["MENU_INTERNAL"]; + $mySource['SourcesAssignedToText'] = $content["LN_GEN_CONFIGFILE"]; + } + // --- + + // --- Set SourceType + if ( $mySource['SourceType'] == SOURCE_DISK ) + { + $mySource['SourcesTypeImage'] = $content["MENU_SOURCE_DISK"]; + $mySource['SourcesTypeText'] = $content["LN_SOURCES_DISK"]; + } + else if ( $mySource['SourceType'] == SOURCE_DB ) + { + $mySource['SourcesTypeImage'] = $content["MENU_SOURCE_DB"]; + $mySource['SourcesTypeText'] = $content["LN_SOURCES_DB"]; + } + else if ( $mySource['SourceType'] == SOURCE_PDO ) + { + $mySource['SourcesTypeImage'] = $content["MENU_SOURCE_PDO"]; + $mySource['SourcesTypeText'] = $content["LN_SOURCES_PDO"]; + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $mySource['cssclass'] = "line1"; + else + $mySource['cssclass'] = "line2"; + $i++; + // --- + } + // --- +// print_r ( $content['SOURCES'] ); +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_SOURCEOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_sources.html"); +$page -> output(); +// --- + ?> \ No newline at end of file diff --git a/src/admin/users.php b/src/admin/users.php index 09331e4..20f91a1 100644 --- a/src/admin/users.php +++ b/src/admin/users.php @@ -1,383 +1,383 @@ - Helps administrating users - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './../'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); - -// Set PAGE to be ADMINPAGE! -define('IS_ADMINPAGE', true); -$content['IS_ADMINPAGE'] = true; -InitPhpLogCon(); -InitSourceConfigs(); -InitFrontEndDefaults(); // Only in WebFrontEnd -InitFilterHelpers(); // Helpers for frontend filtering! - -// Init admin langauge file now! -IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); -// --- - -// --- BEGIN Custom Code - -// Only if the user is an admin! -if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); - -if ( isset($_GET['miniop']) && $_GET['miniop'] == "setisadmin" ) -{ - if ( isset($_GET['id']) && isset($_GET['newval']) ) - { - //PreInit these values - $content['USERID'] = intval(DB_RemoveBadChars($_GET['id'])); - $iNewVal = intval(DB_RemoveBadChars($_GET['newval'])); - - // --- handle special case - if ( $content['USERID'] == $content['SESSION_USERID'] && (!isset($_GET['verify']) || $_GET['verify'] != "yes") && $iNewVal == 0) - { - // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( $content['LN_USER_WARNREMOVEADMIN'], $content['LN_DELETEYES'], $content['LN_DELETENO'] ); - } - // --- - - // Perform SQL Query! - $sqlquery = "SELECT * " . - " FROM " . DB_USERS . - " WHERE ID = " . $content['USERID']; - $result = DB_Query($sqlquery); - $myuser = DB_GetSingleRow($result, true); - if ( isset($myuser['username']) ) - { - // Update is_admin setting! - $result = DB_Query("UPDATE " . DB_USERS . " SET - is_admin = $iNewVal - WHERE ID = " . $content['USERID']); - DB_FreeQuery($result); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = "Error setting is_admin flat, invalid ID, User not found"; - } -} - - -if ( isset($_GET['op']) ) -{ - if ($_GET['op'] == "add") - { - // Set Mode to add - $content['ISEDITORNEWUSER'] = "true"; - $content['USER_FORMACTION'] = "addnewuser"; - $content['USER_SENDBUTTON'] = $content['LN_USER_ADD']; - - //PreInit these values - $content['USERNAME'] = ""; - $content['PASSWORD1'] = ""; - $content['PASSWORD2'] = ""; - } - else if ($_GET['op'] == "edit") - { - // Set Mode to edit - $content['ISEDITORNEWUSER'] = "true"; - $content['USER_FORMACTION'] = "edituser"; - $content['USER_SENDBUTTON'] = $content['LN_USER_EDIT']; - - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['USERID'] = DB_RemoveBadChars($_GET['id']); - - $sqlquery = "SELECT * " . - " FROM " . DB_USERS . - " WHERE ID = " . $content['USERID']; - - $result = DB_Query($sqlquery); - $myuser = DB_GetSingleRow($result, true); - if ( isset($myuser['username']) ) - { - $content['USERID'] = $myuser['ID']; - $content['USERNAME'] = $myuser['username']; - - // Set is_admin flag - if ( $myuser['is_admin'] == 1 ) - $content['CHECKED_ISADMIN'] = "checked"; - else - $content['CHECKED_ISADMIN'] = ""; - - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; - } - } - else if ($_GET['op'] == "delete") - { - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['USERID'] = DB_RemoveBadChars($_GET['id']); - - if ( !isset($_SESSION['SESSION_USERNAME']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDSESSIONS']; - } - else - { - // Get UserInfo - $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['username']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - - if ( $_SESSION['SESSION_USERNAME'] == $myrow['username'] ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_DONOTDELURSLF'], $content['USERID'] ); - } - else - { - // --- Ask for deletion first! - if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) - { - // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_USER_WARNDELETEUSER'], $myrow['username'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); - } - // --- - - // do the delete! - $result = DB_Query( "DELETE FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); - if ($result == FALSE) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_DELUSER'], $content['USERID'] ); - } - else - DB_FreeQuery($result); - - // TODO: DELETE PERSONAL SETTINGS, GROUP MEMBERSHIP ... - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENDEL'], $myrow['username'] ) , "users.php" ); - } - } - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; - } - } -} - -if ( isset($_POST['op']) ) -{ - if ( isset ($_POST['id']) ) { $content['USERID'] = DB_RemoveBadChars($_POST['id']); } else {$content['USERID'] = ""; } - if ( isset ($_POST['username']) ) { $content['USERNAME'] = DB_RemoveBadChars($_POST['username']); } else {$content['USERNAME'] = ""; } - if ( isset ($_POST['password1']) ) { $content['PASSWORD1'] = DB_RemoveBadChars($_POST['password1']); } else {$content['PASSWORD1'] = ""; } - if ( isset ($_POST['password2']) ) { $content['PASSWORD2'] = DB_RemoveBadChars($_POST['password2']); } else {$content['PASSWORD2'] = ""; } - if ( isset ($_POST['isadmin']) ) { $content['ISADMIN'] = 1; } else {$content['ISADMIN'] = 0; } - - - // Check mandotary values - if ( $content['USERNAME'] == "" ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_USEREMPTY']; - } - - if ( !isset($content['ISERROR']) ) - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewuser" ) - { - $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $content['USERNAME'] . "'"); - $myrow = DB_GetSingleRow($result, true); - if ( isset($myrow['username']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_USERNAMETAKEN']; - } - else - { - // Check if Password is set! - if ( strlen($content['PASSWORD1']) <= 0 || - $content['PASSWORD1'] != $content['PASSWORD2'] ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; - } - - if ( !isset($content['ISERROR']) ) - { - // Create passwordhash now :)! - $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); - - // Add new User now! - $result = DB_Query("INSERT INTO " . DB_USERS . " (username, password, is_admin) - VALUES ('" . $content['USERNAME'] . "', - '" . $content['PASSWORDHASH'] . "', - " . $content['ISADMIN'] . ")"); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENADDED'], $content['USERNAME'] ) , "users.php" ); - } - } - } - else if ( $_POST['op'] == "edituser" ) - { - $result = DB_Query("SELECT ID FROM " . DB_USERS . " WHERE ID = " . $content['USERID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); - } - else - { - - // Check if Password is enabled - if ( isset($content['PASSWORD1']) && strlen($content['PASSWORD1']) > 0 ) - { - if ( $content['PASSWORD1'] != $content['PASSWORD2'] ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; - } - - if ( !isset($content['ISERROR']) ) - { - // Create passwordhash now :)! - $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); - - // Edit the User now! - $result = DB_Query("UPDATE " . DB_USERS . " SET - username = '" . $content['USERNAME'] . "', - password = '" . $content['PASSWORDHASH'] . "', - is_admin = " . $content['ISADMIN'] . " - WHERE ID = " . $content['USERID']); - DB_FreeQuery($result); - } - } - else - { - // Edit the User now! - $result = DB_Query("UPDATE " . DB_USERS . " SET - username = '" . $content['USERNAME'] . "', - is_admin = " . $content['ISADMIN'] . " - WHERE ID = " . $content['USERID']); - DB_FreeQuery($result); - } - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENEDIT'], $content['USERNAME']) , "users.php" ); - } - } - } -} - -if ( !isset($_POST['op']) && !isset($_GET['op']) ) -{ - // Default Mode = List Users - $content['LISTUSERS'] = "true"; - - // Read all Serverentries - $sqlquery = "SELECT ID, " . - " username, " . - " is_admin " . - " FROM " . DB_USERS . - " ORDER BY ID "; - $result = DB_Query($sqlquery); - $content['USERS'] = DB_GetAllRows($result, true); - - // --- Process Users - for($i = 0; $i < count($content['USERS']); $i++) - { - // --- Set Image for IsClanMember - if ( $content['USERS'][$i]['is_admin'] == 1 ) - { - $content['USERS'][$i]['is_isadmin_string'] = $content['MENU_SELECTION_ENABLED']; - $content['USERS'][$i]['set_isadmin'] = 0; - } - else - { - $content['USERS'][$i]['is_isadmin_string'] = $content['MENU_SELECTION_DISABLED']; - $content['USERS'][$i]['set_isadmin'] = 1; - } - // --- - - // --- Set CSS Class - if ( $i % 2 == 0 ) - $content['USERS'][$i]['cssclass'] = "line1"; - else - $content['USERS'][$i]['cssclass'] = "line2"; - // --- - } - // --- -} -// --- END Custom Code - -// --- BEGIN CREATE TITLE -$content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_USEROPT']; -// --- END CREATE TITLE - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "admin/admin_users.html"); -$page -> output(); -// --- - + Helps administrating users + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +// Only if the user is an admin! +if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + +if ( isset($_GET['miniop']) && $_GET['miniop'] == "setisadmin" ) +{ + if ( isset($_GET['id']) && isset($_GET['newval']) ) + { + //PreInit these values + $content['USERID'] = intval(DB_RemoveBadChars($_GET['id'])); + $iNewVal = intval(DB_RemoveBadChars($_GET['newval'])); + + // --- handle special case + if ( $content['USERID'] == $content['SESSION_USERID'] && (!isset($_GET['verify']) || $_GET['verify'] != "yes") && $iNewVal == 0) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( $content['LN_USER_WARNREMOVEADMIN'], $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // Perform SQL Query! + $sqlquery = "SELECT * " . + " FROM " . DB_USERS . + " WHERE ID = " . $content['USERID']; + $result = DB_Query($sqlquery); + $myuser = DB_GetSingleRow($result, true); + if ( isset($myuser['username']) ) + { + // Update is_admin setting! + $result = DB_Query("UPDATE " . DB_USERS . " SET + is_admin = $iNewVal + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = "Error setting is_admin flat, invalid ID, User not found"; + } +} + + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWUSER'] = "true"; + $content['USER_FORMACTION'] = "addnewuser"; + $content['USER_SENDBUTTON'] = $content['LN_USER_ADD']; + + //PreInit these values + $content['USERNAME'] = ""; + $content['PASSWORD1'] = ""; + $content['PASSWORD2'] = ""; + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWUSER'] = "true"; + $content['USER_FORMACTION'] = "edituser"; + $content['USER_SENDBUTTON'] = $content['LN_USER_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['USERID'] = DB_RemoveBadChars($_GET['id']); + + $sqlquery = "SELECT * " . + " FROM " . DB_USERS . + " WHERE ID = " . $content['USERID']; + + $result = DB_Query($sqlquery); + $myuser = DB_GetSingleRow($result, true); + if ( isset($myuser['username']) ) + { + $content['USERID'] = $myuser['ID']; + $content['USERNAME'] = $myuser['username']; + + // Set is_admin flag + if ( $myuser['is_admin'] == 1 ) + $content['CHECKED_ISADMIN'] = "checked"; + else + $content['CHECKED_ISADMIN'] = ""; + + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['USERID'] = DB_RemoveBadChars($_GET['id']); + + if ( !isset($_SESSION['SESSION_USERNAME']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDSESSIONS']; + } + else + { + // Get UserInfo + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['username']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + + if ( $_SESSION['SESSION_USERNAME'] == $myrow['username'] ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_DONOTDELURSLF'], $content['USERID'] ); + } + else + { + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_USER_WARNDELETEUSER'], $myrow['username'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_USERS . " WHERE ID = " . $content['USERID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_DELUSER'], $content['USERID'] ); + } + else + DB_FreeQuery($result); + + // TODO: DELETE PERSONAL SETTINGS, GROUP MEMBERSHIP ... + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENDEL'], $myrow['username'] ) , "users.php" ); + } + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_INVALIDID']; + } + } +} + +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['USERID'] = DB_RemoveBadChars($_POST['id']); } else {$content['USERID'] = ""; } + if ( isset ($_POST['username']) ) { $content['USERNAME'] = DB_RemoveBadChars($_POST['username']); } else {$content['USERNAME'] = ""; } + if ( isset ($_POST['password1']) ) { $content['PASSWORD1'] = DB_RemoveBadChars($_POST['password1']); } else {$content['PASSWORD1'] = ""; } + if ( isset ($_POST['password2']) ) { $content['PASSWORD2'] = DB_RemoveBadChars($_POST['password2']); } else {$content['PASSWORD2'] = ""; } + if ( isset ($_POST['isadmin']) ) { $content['ISADMIN'] = 1; } else {$content['ISADMIN'] = 0; } + + + // Check mandotary values + if ( $content['USERNAME'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_USEREMPTY']; + } + + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewuser" ) + { + $result = DB_Query("SELECT username FROM " . DB_USERS . " WHERE username = '" . $content['USERNAME'] . "'"); + $myrow = DB_GetSingleRow($result, true); + if ( isset($myrow['username']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_USERNAMETAKEN']; + } + else + { + // Check if Password is set! + if ( strlen($content['PASSWORD1']) <= 0 || + $content['PASSWORD1'] != $content['PASSWORD2'] ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; + } + + if ( !isset($content['ISERROR']) ) + { + // Create passwordhash now :)! + $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); + + // Add new User now! + $result = DB_Query("INSERT INTO " . DB_USERS . " (username, password, is_admin) + VALUES ('" . $content['USERNAME'] . "', + '" . $content['PASSWORDHASH'] . "', + " . $content['ISADMIN'] . ")"); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENADDED'], $content['USERNAME'] ) , "users.php" ); + } + } + } + else if ( $_POST['op'] == "edituser" ) + { + $result = DB_Query("SELECT ID FROM " . DB_USERS . " WHERE ID = " . $content['USERID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_USER_ERROR_IDNOTFOUND'], $content['USERID'] ); + } + else + { + + // Check if Password is enabled + if ( isset($content['PASSWORD1']) && strlen($content['PASSWORD1']) > 0 ) + { + if ( $content['PASSWORD1'] != $content['PASSWORD2'] ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_USER_ERROR_PASSSHORT']; + } + + if ( !isset($content['ISERROR']) ) + { + // Create passwordhash now :)! + $content['PASSWORDHASH'] = md5( $content['PASSWORD1'] ); + + // Edit the User now! + $result = DB_Query("UPDATE " . DB_USERS . " SET + username = '" . $content['USERNAME'] . "', + password = '" . $content['PASSWORDHASH'] . "', + is_admin = " . $content['ISADMIN'] . " + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + } + else + { + // Edit the User now! + $result = DB_Query("UPDATE " . DB_USERS . " SET + username = '" . $content['USERNAME'] . "', + is_admin = " . $content['ISADMIN'] . " + WHERE ID = " . $content['USERID']); + DB_FreeQuery($result); + } + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_USER_ERROR_HASBEENEDIT'], $content['USERNAME']) , "users.php" ); + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Users + $content['LISTUSERS'] = "true"; + + // Read all Serverentries + $sqlquery = "SELECT ID, " . + " username, " . + " is_admin " . + " FROM " . DB_USERS . + " ORDER BY ID "; + $result = DB_Query($sqlquery); + $content['USERS'] = DB_GetAllRows($result, true); + + // --- Process Users + for($i = 0; $i < count($content['USERS']); $i++) + { + // --- Set Image for IsClanMember + if ( $content['USERS'][$i]['is_admin'] == 1 ) + { + $content['USERS'][$i]['is_isadmin_string'] = $content['MENU_SELECTION_ENABLED']; + $content['USERS'][$i]['set_isadmin'] = 0; + } + else + { + $content['USERS'][$i]['is_isadmin_string'] = $content['MENU_SELECTION_DISABLED']; + $content['USERS'][$i]['set_isadmin'] = 1; + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $content['USERS'][$i]['cssclass'] = "line1"; + else + $content['USERS'][$i]['cssclass'] = "line2"; + // --- + } + // --- +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_USEROPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_users.html"); +$page -> output(); +// --- + ?> \ No newline at end of file diff --git a/src/admin/views.php b/src/admin/views.php index eb50457..6ef2444 100644 --- a/src/admin/views.php +++ b/src/admin/views.php @@ -1,577 +1,577 @@ - Helps administrating custom user views - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './../'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -include($gl_root_path . 'include/functions_filters.php'); - -// Set PAGE to be ADMINPAGE! -define('IS_ADMINPAGE', true); -$content['IS_ADMINPAGE'] = true; -InitPhpLogCon(); -InitSourceConfigs(); -InitFrontEndDefaults(); // Only in WebFrontEnd -InitFilterHelpers(); // Helpers for frontend filtering! - -// Init admin langauge file now! -IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); -// --- - -// --- BEGIN Custom Code - -// Only if the user is an admin! -//if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) -// DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); - -if ( isset($_GET['op']) ) -{ - if ($_GET['op'] == "add") - { - // Set Mode to add - $content['ISEDITORNEWVIEW'] = "true"; - $content['VIEW_FORMACTION'] = "addnewview"; - $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_ADD']; - - //PreInit these values - $content['DisplayName'] = ""; - $content['userid'] = null; - $content['CHECKED_ISUSERONLY'] = ""; - $content['VIEWID'] = ""; - - // --- Check if groups are available - $content['SUBGROUPS'] = GetGroupsForSelectfield(); - if ( is_array($content['SUBGROUPS']) ) - $content['ISGROUPSAVAILABLE'] = true; - else - $content['ISGROUPSAVAILABLE'] = false; - // --- - } - else if ($_GET['op'] == "edit") - { - // Set Mode to edit - $content['ISEDITORNEWVIEW'] = "true"; - $content['VIEW_FORMACTION'] = "editview"; - $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_EDIT']; - - - // Copy Views array for further modifications - $content['VIEWS'] = $content['Views']; - - // View must be loaded as well already! - if ( isset($_GET['id']) && isset($content['VIEWS'][$_GET['id']]) ) - { - //PreInit these values - $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); - if ( isset($content['VIEWS'][ $content['VIEWID'] ]) ) - { - $myview = $content['VIEWS'][ $content['VIEWID'] ]; - - $content['DisplayName'] = $myview['DisplayName'] ; - $content['userid'] = $myview['userid']; - $content['Columns'] = $myview['Columns']; - if ( $content['userid'] != null ) - $content['CHECKED_ISUSERONLY'] = "checked"; - else - $content['CHECKED_ISUSERONLY'] = ""; - - // --- Check if groups are available - $content['SUBGROUPS'] = GetGroupsForSelectfield(); - if ( is_array($content['SUBGROUPS']) ) - { - // Process All Groups - for($i = 0; $i < count($content['SUBGROUPS']); $i++) - { - if ( $myview['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $myview['groupid'] ) - $content['SUBGROUPS'][$i]['group_selected'] = "selected"; - else - $content['SUBGROUPS'][$i]['group_selected'] = ""; - } - - // Enable Group Selection - $content['ISGROUPSAVAILABLE'] = true; - } - else - $content['ISGROUPSAVAILABLE'] = false; - // --- - } - else - { - $content['ISEDITORNEWVIEW'] = false; - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); - } - } - else - { - $content['ISEDITORNEWVIEW'] = false; - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_INVALIDID'], isset($_GET['id']) ? $_GET['id'] : "" ); - } - } - else if ($_GET['op'] == "delete") - { - if ( isset($_GET['id']) ) - { - //PreInit these values - $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); - - // Get UserInfo - $result = DB_Query("SELECT DisplayName FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID'] ); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['DisplayName']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); - } - - // --- Ask for deletion first! - if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) - { - // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_VIEWS_WARNDELETEVIEW'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); - } - // --- - - // do the delete! - $result = DB_Query( "DELETE FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID'] ); - if ($result == FALSE) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_DELSEARCH'], $content['VIEWID'] ); - } - else - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "views.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_INVALIDID']; - } - } -} - -// --- Additional work todo for the edit view -if ( isset($content['ISEDITORNEWVIEW']) && $content['ISEDITORNEWVIEW'] ) -{ - // If Columns are send using POST we use them, otherwise we try to use from the view itself, if available - if ( isset($_POST['Columns']) ) - $AllColumns = $_POST['Columns']; - else if ( isset($content['Columns']) ) - $AllColumns = $content['Columns']; - - - // Read Columns from FORM data! - if ( isset($AllColumns) ) - { - // --- Read Columns from Formdata - if ( is_array($AllColumns) ) - { - // Copy columns ID's - foreach ($AllColumns as $myColKey) - $content['SUBCOLUMNS'][$myColKey]['ColFieldID'] = $myColKey; - } - else // One element only - $content['SUBCOLUMNS'][$AllColumns]['ColFieldID'] = $AllColumns; - // --- - - // --- Process Columns for display - $i = 0; // Help counter! - foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) - { - // Set Fieldcaption - if ( isset($fields[$key]) && isset($content[ $fields[$key]['FieldCaptionID'] ]) ) - $myColumn['ColCaption'] = $content[ $fields[$key]['FieldCaptionID'] ]; - else - $myColumn['ColCaption'] = $key; - - // --- Set CSS Class - if ( $i % 2 == 0 ) - $myColumn['colcssclass'] = "line1"; - else - $myColumn['colcssclass'] = "line2"; - $i++; - // --- - } - // --- - } - - // --- Copy fields data array - $content['FIELDS'] = $fields; - - // removed already added fields - if ( isset($content['SUBCOLUMNS']) ) - { - foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) - { - if ( isset($content['FIELDS'][$key]) ) - unset($content['FIELDS'][$key]); - } - } - - // set fieldcaption - foreach ($content['FIELDS'] as $key => &$myField ) - { - // Set Fieldcaption - if ( isset($content[ $myField['FieldCaptionID'] ]) ) - $myField['FieldCaption'] = $content[ $myField['FieldCaptionID'] ]; - else - $myField['FieldCaption'] = $key; - } - // --- -} -// --- - -// --- Process POST Form Data -if ( isset($_POST['op']) ) -{ - if ( isset ($_POST['id']) ) { $content['VIEWID'] = DB_RemoveBadChars($_POST['id']); } else {$content['VIEWID'] = ""; } - if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } - - // User & Group handeled specially - if ( isset ($_POST['isuseronly']) ) - { - $content['userid'] = $content['SESSION_USERID']; - $content['groupid'] = "null"; // Either user or group not both! - } - else - { - $content['userid'] = "null"; - if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) - $content['groupid'] = intval($_POST['groupid']); - else - $content['groupid'] = "null"; - } - - // --- Check mandotary values - if ( $content['DisplayName'] == "" ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY']; - } - // --- - - if ( !isset($content['ISERROR']) ) - { - // Check subop's first! - if ( isset($_POST['subop']) ) - { - // Get NewColID - $szColId = DB_RemoveBadChars($_POST['newcolumn']); - - // Add a new Column into our list! - if ( $_POST['subop'] == $content['LN_VIEWS_ADDCOLUMN'] && isset($_POST['newcolumn']) ) - { - // Add New entry into columnlist - $content['SUBCOLUMNS'][$szColId]['ColFieldID'] = $szColId; - - // Set Fieldcaption - if ( isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) - $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; - else - $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $szColId; - - // Set CSSClass - $content['SUBCOLUMNS'][$szColId]['colcssclass'] = count($content['SUBCOLUMNS']) % 2 == 0 ? "line1" : "line2"; - - // Remove from fields list as well - if ( isset($content['FIELDS'][$szColId]) ) - unset($content['FIELDS'][$szColId]); - - } - } - else if ( isset($_POST['subop_delete']) ) - { - // Get Column ID - $szColId = DB_RemoveBadChars($_POST['subop_delete']); - - // Remove Entry from Columnslist - if ( isset($content['SUBCOLUMNS'][$szColId]) ) - unset($content['SUBCOLUMNS'][$szColId]); - - // Add removed entry to field list - $content['FIELDS'][$szColId] = $szColId; - - // Set Fieldcaption - if ( isset($fields[$szColId]) && isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) - $content['FIELDS'][$szColId]['FieldCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; - else - $content['FIELDS'][$szColId]['FieldCaption'] = $szColId; - } - else if ( isset($_POST['subop_moveup']) ) - { - // Get Column ID - $szColId = DB_RemoveBadChars($_POST['subop_moveup']); - - // --- Move Entry one UP in Columnslist - // Find the entry in the array - $iArrayNum = 0; - foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) - { - if ( $key == $szColId ) - break; - - $iArrayNum++; - } - - // If found move up - if ( $iArrayNum > 0 ) - { - // Extract Entry from the array - $EntryTwoMove = array_slice($content['SUBCOLUMNS'], $iArrayNum, 1); - - // Unset Entry from the array - unset( $content['SUBCOLUMNS'][$szColId] ); - - // Splice the array order! - array_splice($content['SUBCOLUMNS'], $iArrayNum-1, 0, $EntryTwoMove); - } - // --- - } - else if ( isset($_POST['subop_movedown']) ) - { - // Get Column ID - $szColId = DB_RemoveBadChars($_POST['subop_movedown']); - - // --- Move Entry one DOWN in Columnslist - // Find the entry in the array - $iArrayNum = 0; - foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) - { - if ( $key == $szColId ) - break; - - $iArrayNum++; - } - - // If found move down - if ( $iArrayNum < count($content['SUBCOLUMNS']) ) - { - // Extract Entry from the array - $EntryTwoMove = array_slice($content['SUBCOLUMNS'], $iArrayNum, 1); - - // Unset Entry from the array - unset( $content['SUBCOLUMNS'][$szColId] ); - - // Splice the array order! - array_splice($content['SUBCOLUMNS'], $iArrayNum+1, 0, $EntryTwoMove); - } - // --- - } - else // Now SUBOP means normal processing! - { - // Everything was alright, so we go to the next step! - if ( $_POST['op'] == "addnewview" ) - { - // Create Columnlist comma seperated! - if ( isset($_POST['Columns']) && is_array($_POST['Columns']) ) - { - // Copy columns ID's - foreach ($_POST['Columns'] as $myColKey) - { - if ( isset($content['Columns']) ) - $content['Columns'] .= ", " . $myColKey; - else - $content['Columns'] = $myColKey; - } - - // Add custom search now! - $sqlquery = "INSERT INTO " . DB_VIEWS. " (DisplayName, Columns, userid, groupid) - VALUES ('" . $content['DisplayName'] . "', - '" . $content['Columns'] . "', - " . $content['userid'] . ", - " . $content['groupid'] . " - )"; - $result = DB_Query($sqlquery); - DB_FreeQuery($result); - - // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_HASBEENADDED'], $content['DisplayName'] ) , "views.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_NOCOLUMNS']; - } - } - else if ( $_POST['op'] == "editview" ) - { - $result = DB_Query("SELECT ID FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID']); - $myrow = DB_GetSingleRow($result, true); - if ( !isset($myrow['ID']) ) - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); - } - else - { - // Create Columnlist comma seperated! - if ( isset($_POST['Columns']) && is_array($_POST['Columns']) ) - { - // Copy columns ID's - unset($content['Columns']); - foreach ($_POST['Columns'] as $myColKey) - { - if ( isset($content['Columns']) ) - $content['Columns'] .= ", " . $myColKey; - else - $content['Columns'] = $myColKey; - } - - - // Edit the Search Entry now! - $result = DB_Query("UPDATE " . DB_VIEWS . " SET - DisplayName = '" . $content['DisplayName'] . "', - Columns = '" . $content['Columns'] . "', - userid = " . $content['userid'] . ", - groupid = " . $content['groupid'] . " - WHERE ID = " . $content['VIEWID']); - DB_FreeQuery($result); - - // Done redirect! - RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_HASBEENEDIT'], $content['DisplayName']) , "views.php" ); - } - else - { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_NOCOLUMNS']; - } - } - } - } - } -} - -if ( !isset($_POST['op']) && !isset($_GET['op']) ) -{ - // Default Mode = List Searches - $content['LISTVIEWS'] = "true"; - - // Copy Views array for further modifications - $content['VIEWS'] = $content['Views']; - - // --- Process Views - $i = 0; // Help counter! - foreach ($content['VIEWS'] as &$myView ) - { - // So internal Views can not be edited but seen - if ( is_numeric($myView['ID']) ) - { - $myView['ActionsAllowed'] = true; - - // --- Set Image for Type - if ( $myView['userid'] != null ) - { - $myView['ViewTypeImage'] = $content["MENU_ADMINUSERS"]; - $myView['ViewTypeText'] = $content["LN_GEN_USERONLY"]; - } - else if ( $myView['groupid'] != null ) - { - $myView['ViewTypeImage'] = $content["MENU_ADMINGROUPS"]; - $myView['ViewTypeText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $myView['groupname'] ); - - // Check if is ADMIN User, deny if normal user! - if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - $myView['ActionsAllowed'] = false; - } - else - { - $myView['ViewTypeImage'] = $content["MENU_GLOBAL"]; - $myView['ViewTypeText'] = $content["LN_GEN_GLOBAL"]; - - // Check if is ADMIN User, deny if normal user! - if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) - $myView['ActionsAllowed'] = false; - } - // --- - } - else - { - $myView['ActionsAllowed'] = false; - - $myView['ViewTypeImage'] = $content["MENU_INTERNAL"]; - $myView['ViewTypeText'] = $content["LN_GEN_INTERNAL"]; - } - - // --- Add DisplayNames to columns - $iBegin = true; - foreach ($myView['Columns'] as $myCol ) - { - // Get Fieldcaption - if ( isset($fields[$myCol]) && isset($content[ $fields[$myCol]['FieldCaptionID'] ]) ) - $myView['COLUMNS'][$myCol]['FieldCaption'] = $content[ $fields[$myCol]['FieldCaptionID'] ]; - else - $myView['COLUMNS'][$myCol]['FieldCaption'] = $myCol; - - if ( $iBegin ) - { - $myView['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ""; - $iBegin = false; - } - else - $myView['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ", "; - - } - // --- - - // --- Set CSS Class - if ( $i % 2 == 0 ) - $myView['cssclass'] = "line1"; - else - $myView['cssclass'] = "line2"; - $i++; - // --- - } - // --- -} -// --- END Custom Code - -// --- BEGIN CREATE TITLE -$content['TITLE'] = InitPageTitle(); -$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_VIEWSOPT']; -// --- END CREATE TITLE - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "admin/admin_views.html"); -$page -> output(); -// --- - + Helps administrating custom user views + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +// Only if the user is an admin! +//if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) +// DieWithFriendlyErrorMsg( $content['LN_ADMIN_ERROR_NOTALLOWED'] ); + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWVIEW'] = "true"; + $content['VIEW_FORMACTION'] = "addnewview"; + $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_ADD']; + + //PreInit these values + $content['DisplayName'] = ""; + $content['userid'] = null; + $content['CHECKED_ISUSERONLY'] = ""; + $content['VIEWID'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + $content['ISGROUPSAVAILABLE'] = true; + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWVIEW'] = "true"; + $content['VIEW_FORMACTION'] = "editview"; + $content['VIEW_SENDBUTTON'] = $content['LN_VIEWS_EDIT']; + + + // Copy Views array for further modifications + $content['VIEWS'] = $content['Views']; + + // View must be loaded as well already! + if ( isset($_GET['id']) && isset($content['VIEWS'][$_GET['id']]) ) + { + //PreInit these values + $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); + if ( isset($content['VIEWS'][ $content['VIEWID'] ]) ) + { + $myview = $content['VIEWS'][ $content['VIEWID'] ]; + + $content['DisplayName'] = $myview['DisplayName'] ; + $content['userid'] = $myview['userid']; + $content['Columns'] = $myview['Columns']; + if ( $content['userid'] != null ) + $content['CHECKED_ISUSERONLY'] = "checked"; + else + $content['CHECKED_ISUSERONLY'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $myview['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $myview['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else + { + $content['ISEDITORNEWVIEW'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); + } + } + else + { + $content['ISEDITORNEWVIEW'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_INVALIDID'], isset($_GET['id']) ? $_GET['id'] : "" ); + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['VIEWID'] = DB_RemoveBadChars($_GET['id']); + + // Get UserInfo + $result = DB_Query("SELECT DisplayName FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['DisplayName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); + } + + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_VIEWS_WARNDELETEVIEW'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_DELSEARCH'], $content['VIEWID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "views.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_INVALIDID']; + } + } +} + +// --- Additional work todo for the edit view +if ( isset($content['ISEDITORNEWVIEW']) && $content['ISEDITORNEWVIEW'] ) +{ + // If Columns are send using POST we use them, otherwise we try to use from the view itself, if available + if ( isset($_POST['Columns']) ) + $AllColumns = $_POST['Columns']; + else if ( isset($content['Columns']) ) + $AllColumns = $content['Columns']; + + + // Read Columns from FORM data! + if ( isset($AllColumns) ) + { + // --- Read Columns from Formdata + if ( is_array($AllColumns) ) + { + // Copy columns ID's + foreach ($AllColumns as $myColKey) + $content['SUBCOLUMNS'][$myColKey]['ColFieldID'] = $myColKey; + } + else // One element only + $content['SUBCOLUMNS'][$AllColumns]['ColFieldID'] = $AllColumns; + // --- + + // --- Process Columns for display + $i = 0; // Help counter! + foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) + { + // Set Fieldcaption + if ( isset($fields[$key]) && isset($content[ $fields[$key]['FieldCaptionID'] ]) ) + $myColumn['ColCaption'] = $content[ $fields[$key]['FieldCaptionID'] ]; + else + $myColumn['ColCaption'] = $key; + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $myColumn['colcssclass'] = "line1"; + else + $myColumn['colcssclass'] = "line2"; + $i++; + // --- + } + // --- + } + + // --- Copy fields data array + $content['FIELDS'] = $fields; + + // removed already added fields + if ( isset($content['SUBCOLUMNS']) ) + { + foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) + { + if ( isset($content['FIELDS'][$key]) ) + unset($content['FIELDS'][$key]); + } + } + + // set fieldcaption + foreach ($content['FIELDS'] as $key => &$myField ) + { + // Set Fieldcaption + if ( isset($content[ $myField['FieldCaptionID'] ]) ) + $myField['FieldCaption'] = $content[ $myField['FieldCaptionID'] ]; + else + $myField['FieldCaption'] = $key; + } + // --- +} +// --- + +// --- Process POST Form Data +if ( isset($_POST['op']) ) +{ + if ( isset ($_POST['id']) ) { $content['VIEWID'] = DB_RemoveBadChars($_POST['id']); } else {$content['VIEWID'] = ""; } + if ( isset ($_POST['DisplayName']) ) { $content['DisplayName'] = DB_RemoveBadChars($_POST['DisplayName']); } else {$content['DisplayName'] = ""; } + + // User & Group handeled specially + if ( isset ($_POST['isuseronly']) ) + { + $content['userid'] = $content['SESSION_USERID']; + $content['groupid'] = "null"; // Either user or group not both! + } + else + { + $content['userid'] = "null"; + if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) + $content['groupid'] = intval($_POST['groupid']); + else + $content['groupid'] = "null"; + } + + // --- Check mandotary values + if ( $content['DisplayName'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY']; + } + // --- + + if ( !isset($content['ISERROR']) ) + { + // Check subop's first! + if ( isset($_POST['subop']) ) + { + // Get NewColID + $szColId = DB_RemoveBadChars($_POST['newcolumn']); + + // Add a new Column into our list! + if ( $_POST['subop'] == $content['LN_VIEWS_ADDCOLUMN'] && isset($_POST['newcolumn']) ) + { + // Add New entry into columnlist + $content['SUBCOLUMNS'][$szColId]['ColFieldID'] = $szColId; + + // Set Fieldcaption + if ( isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) + $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; + else + $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $szColId; + + // Set CSSClass + $content['SUBCOLUMNS'][$szColId]['colcssclass'] = count($content['SUBCOLUMNS']) % 2 == 0 ? "line1" : "line2"; + + // Remove from fields list as well + if ( isset($content['FIELDS'][$szColId]) ) + unset($content['FIELDS'][$szColId]); + + } + } + else if ( isset($_POST['subop_delete']) ) + { + // Get Column ID + $szColId = DB_RemoveBadChars($_POST['subop_delete']); + + // Remove Entry from Columnslist + if ( isset($content['SUBCOLUMNS'][$szColId]) ) + unset($content['SUBCOLUMNS'][$szColId]); + + // Add removed entry to field list + $content['FIELDS'][$szColId] = $szColId; + + // Set Fieldcaption + if ( isset($fields[$szColId]) && isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) + $content['FIELDS'][$szColId]['FieldCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; + else + $content['FIELDS'][$szColId]['FieldCaption'] = $szColId; + } + else if ( isset($_POST['subop_moveup']) ) + { + // Get Column ID + $szColId = DB_RemoveBadChars($_POST['subop_moveup']); + + // --- Move Entry one UP in Columnslist + // Find the entry in the array + $iArrayNum = 0; + foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) + { + if ( $key == $szColId ) + break; + + $iArrayNum++; + } + + // If found move up + if ( $iArrayNum > 0 ) + { + // Extract Entry from the array + $EntryTwoMove = array_slice($content['SUBCOLUMNS'], $iArrayNum, 1); + + // Unset Entry from the array + unset( $content['SUBCOLUMNS'][$szColId] ); + + // Splice the array order! + array_splice($content['SUBCOLUMNS'], $iArrayNum-1, 0, $EntryTwoMove); + } + // --- + } + else if ( isset($_POST['subop_movedown']) ) + { + // Get Column ID + $szColId = DB_RemoveBadChars($_POST['subop_movedown']); + + // --- Move Entry one DOWN in Columnslist + // Find the entry in the array + $iArrayNum = 0; + foreach ($content['SUBCOLUMNS'] as $key => &$myColumn ) + { + if ( $key == $szColId ) + break; + + $iArrayNum++; + } + + // If found move down + if ( $iArrayNum < count($content['SUBCOLUMNS']) ) + { + // Extract Entry from the array + $EntryTwoMove = array_slice($content['SUBCOLUMNS'], $iArrayNum, 1); + + // Unset Entry from the array + unset( $content['SUBCOLUMNS'][$szColId] ); + + // Splice the array order! + array_splice($content['SUBCOLUMNS'], $iArrayNum+1, 0, $EntryTwoMove); + } + // --- + } + else // Now SUBOP means normal processing! + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewview" ) + { + // Create Columnlist comma seperated! + if ( isset($_POST['Columns']) && is_array($_POST['Columns']) ) + { + // Copy columns ID's + foreach ($_POST['Columns'] as $myColKey) + { + if ( isset($content['Columns']) ) + $content['Columns'] .= ", " . $myColKey; + else + $content['Columns'] = $myColKey; + } + + // Add custom search now! + $sqlquery = "INSERT INTO " . DB_VIEWS. " (DisplayName, Columns, userid, groupid) + VALUES ('" . $content['DisplayName'] . "', + '" . $content['Columns'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_HASBEENADDED'], $content['DisplayName'] ) , "views.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_NOCOLUMNS']; + } + } + else if ( $_POST['op'] == "editview" ) + { + $result = DB_Query("SELECT ID FROM " . DB_VIEWS . " WHERE ID = " . $content['VIEWID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_VIEWS_ERROR_IDNOTFOUND'], $content['VIEWID'] ); + } + else + { + // Create Columnlist comma seperated! + if ( isset($_POST['Columns']) && is_array($_POST['Columns']) ) + { + // Copy columns ID's + unset($content['Columns']); + foreach ($_POST['Columns'] as $myColKey) + { + if ( isset($content['Columns']) ) + $content['Columns'] .= ", " . $myColKey; + else + $content['Columns'] = $myColKey; + } + + + // Edit the Search Entry now! + $result = DB_Query("UPDATE " . DB_VIEWS . " SET + DisplayName = '" . $content['DisplayName'] . "', + Columns = '" . $content['Columns'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['VIEWID']); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_VIEWS_HASBEENEDIT'], $content['DisplayName']) , "views.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_VIEWS_ERROR_NOCOLUMNS']; + } + } + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Searches + $content['LISTVIEWS'] = "true"; + + // Copy Views array for further modifications + $content['VIEWS'] = $content['Views']; + + // --- Process Views + $i = 0; // Help counter! + foreach ($content['VIEWS'] as &$myView ) + { + // So internal Views can not be edited but seen + if ( is_numeric($myView['ID']) ) + { + $myView['ActionsAllowed'] = true; + + // --- Set Image for Type + if ( $myView['userid'] != null ) + { + $myView['ViewTypeImage'] = $content["MENU_ADMINUSERS"]; + $myView['ViewTypeText'] = $content["LN_GEN_USERONLY"]; + } + else if ( $myView['groupid'] != null ) + { + $myView['ViewTypeImage'] = $content["MENU_ADMINGROUPS"]; + $myView['ViewTypeText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $myView['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $myView['ActionsAllowed'] = false; + } + else + { + $myView['ViewTypeImage'] = $content["MENU_GLOBAL"]; + $myView['ViewTypeText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $myView['ActionsAllowed'] = false; + } + // --- + } + else + { + $myView['ActionsAllowed'] = false; + + $myView['ViewTypeImage'] = $content["MENU_INTERNAL"]; + $myView['ViewTypeText'] = $content["LN_GEN_INTERNAL"]; + } + + // --- Add DisplayNames to columns + $iBegin = true; + foreach ($myView['Columns'] as $myCol ) + { + // Get Fieldcaption + if ( isset($fields[$myCol]) && isset($content[ $fields[$myCol]['FieldCaptionID'] ]) ) + $myView['COLUMNS'][$myCol]['FieldCaption'] = $content[ $fields[$myCol]['FieldCaptionID'] ]; + else + $myView['COLUMNS'][$myCol]['FieldCaption'] = $myCol; + + if ( $iBegin ) + { + $myView['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ""; + $iBegin = false; + } + else + $myView['COLUMNS'][$myCol]['FieldCaptionSeperator'] = ", "; + + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $myView['cssclass'] = "line1"; + else + $myView['cssclass'] = "line2"; + $i++; + // --- + } + // --- +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_VIEWSOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_views.html"); +$page -> output(); +// --- + ?> \ No newline at end of file diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php new file mode 100644 index 0000000..a3391a6 --- /dev/null +++ b/src/lang/de/admin.php @@ -0,0 +1,211 @@ + www.phplogcon.org <- + * ----------------------------------------------------------------- + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ +global $content; + +// Global Stuff +$content['LN_ADMINMENU_HOMEPAGE'] = "Back to Show Events"; +$content['LN_ADMINMENU_GENOPT'] = "General Options"; +$content['LN_ADMINMENU_SOURCEOPT'] = "Sources Options"; +$content['LN_ADMINMENU_VIEWSOPT'] = "Views Options"; +$content['LN_ADMINMENU_SEARCHOPT'] = "Search Options"; +$content['LN_ADMINMENU_USEROPT'] = "User Options"; +$content['LN_ADMINMENU_GROUPOPT'] = "Group Options"; +$content['LN_ADMIN_CENTER'] = "Admin center"; +$content['LN_ADMIN_UNKNOWNSTATE'] = "Unknown State"; +$content['LN_ADMIN_ERROR_NOTALLOWED'] = "You are not allowed to access this page with your user level."; +$content['LN_DELETEYES'] = "Yes"; +$content['LN_DELETENO'] = "No"; +$content['LN_GEN_ACTIONS'] = "Available Actions"; +$content['LN_ADMIN_SEND'] = "Send changes"; +$content['LN_GEN_USERONLY'] = "User only"; +$content['LN_GEN_GROUPONLY'] = "Group only"; +$content['LN_GEN_GLOBAL'] = "Global"; +$content['LN_GEN_USERONLY_LONG'] = "For me only
(Only available to your user)"; +$content['LN_GEN_GROUPONLY_LONG'] = "For this group
(Only available to the selected group)"; +$content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; + + +// General Options +$content['LN_ADMIN_GLOBFRONTEND'] = "Global frontend options"; +$content['LN_ADMIN_USERFRONTEND'] = "User specific frontend options"; +$content['LN_ADMIN_MISC'] = "Miscellaneous Options"; +$content['LN_GEN_SHOWDEBUGMSG'] = "Show Debug messages"; +$content['LN_GEN_DEBUGGRIDCOUNTER'] = "Show Debug Gridcounter"; +$content['LN_GEN_SHOWPAGERENDERSTATS'] = "Show Pagerenderstats"; +$content['LN_GEN_ENABLEGZIP'] = "Enable GZIP Compressed Output"; +$content['LN_GEN_DEBUGUSERLOGIN'] = "Debug Userlogin"; +$content['LN_GEN_WEBSTYLE'] = "Default selected style"; +$content['LN_GEN_SELLANGUAGE'] = "Default selected language"; +$content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; +$content['LN_GEN_USETODAY'] = "Use Today and Yesterday in timefields"; +$content['LN_GEN_DETAILPOPUPS'] = "Use Popup to display the full messagedetails"; +$content['LN_GEN_MSGCHARLIMIT'] = "Character limit of the message in main view"; +$content['LN_GEN_ENTRIESPERPAGE'] = "Number of entries per page"; +$content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; +$content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; +$content['LN_GEN_CUSTBTNCAPT'] = "Custom search caption"; +$content['LN_GEN_CUSTBTNSRCH'] = "Custom search string"; +$content['LN_GEN_SUCCESSFULLYSAVED'] = "The configuration Values have been successfully saved"; +$content['LN_GEN_INTERNAL'] = "Internal"; +$content['LN_GEN_DISABLED'] = "Function disabled"; +$content['LN_GEN_CONFIGFILE'] = "Configuration File"; +$content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; + +// User Center +$content['LN_USER_CENTER'] = "User Options"; +$content['LN_USER_ID'] = "ID"; +$content['LN_USER_NAME'] = "Username"; +$content['LN_USER_ADD'] = "Add User"; +$content['LN_USER_EDIT'] = "Edit User"; +$content['LN_USER_DELETE'] = "Delete User"; +$content['LN_USER_PASSWORD1'] = "Password"; +$content['LN_USER_PASSWORD2'] = "Confirm Password"; +$content['LN_USER_ERROR_IDNOTFOUND'] = "Error, User with ID '%1' , was not found"; +$content['LN_USER_ERROR_DONOTDELURSLF'] = "Error, you can not DELETE YOURSELF!"; +$content['LN_USER_ERROR_DELUSER'] = "Deleting of the user with id '%1' failed!"; +$content['LN_USER_ERROR_INVALIDID'] = "Error, invalid ID, User not found"; +$content['LN_USER_ERROR_HASBEENDEL'] = "The User '%1' has been successfully deleted!"; +$content['LN_USER_ERROR_USEREMPTY'] = "Error, Username was empty"; +$content['LN_USER_ERROR_USERNAMETAKEN'] = "Error, this Username is already taken!"; +$content['LN_USER_ERROR_PASSSHORT'] = "Error, Password was to short, or did not match"; +$content['LN_USER_ERROR_HASBEENADDED'] = "User '%1' has been successfully added"; +$content['LN_USER_ERROR_HASBEENEDIT'] = "User '%1' has been successfully edited"; +$content['LN_USER_ISADMIN'] = "Is Admin?"; +$content['LN_USER_ADDEDIT'] = "Add/Edit User"; +$content['LN_USER_WARNREMOVEADMIN'] = "You are about to revoke your own administrative priviledges. Are you sure to remove your admin status?"; +$content['LN_USER_WARNDELETEUSER'] = "Are you sure that you want to delete the User '%1'? All his personal settings will be deleted as well."; +$content['LN_USER_ERROR_INVALIDSESSIONS'] = "Invalid User Session."; +$content['LN_USER_'] = ""; + +// Group center +$content['LN_GROUP_CENTER'] = "Group Center"; +$content['LN_GROUP_ID'] = "ID"; +$content['LN_GROUP_NAME'] = "Groupname"; +$content['LN_GROUP_DESCRIPTION'] = "Groupdescription"; +$content['LN_GROUP_TYPE'] = "Grouptype"; +$content['LN_GROUP_ADD'] = "Add Group"; +$content['LN_GROUP_EDIT'] = "Edit Group"; +$content['LN_GROUP_DELETE'] = "Delete Group"; +$content['LN_GROUP_NOGROUPS'] = "No groups have been added yet"; +$content['LN_GROUP_ADDEDIT'] = "Add/Edit Group"; +$content['LN_GROUP_ERROR_GROUPEMPTY'] = "The groupname cannot be empty."; +$content['LN_GROUP_ERROR_GROUPNAMETAKEN'] = "The groupname has already been taken."; +$content['LN_GROUP_HASBEENADDED'] = "The group '%1' has been successfully added."; +$content['LN_GROUP_ERROR_IDNOTFOUND'] = "The group with ID '%1' could not be found."; +$content['LN_GROUP_ERROR_HASBEENEDIT'] = "The group '%1' has been successfully edited."; +$content['LN_GROUP_ERROR_INVALIDGROUP'] = "Error, invalid ID, Group not found"; +$content['LN_GROUP_WARNDELETEGROUP'] = "Are you sure that you want to delete the Group '%1'? All Groupsettings will be deleted as well."; +$content['LN_GROUP_ERROR_DELGROUP'] = "Deleting of the group with id '%1' failed!"; +$content['LN_GROUP_ERROR_HASBEENDEL'] = "The Group '%1' has been successfully deleted!"; +$content['LN_GROUP_MEMBERS'] = "Groupmembers: "; +$content['LN_GROUP_ADDUSER'] = "Add User to Group"; +$content['LN_GROUP_ERROR_USERIDMISSING'] = "The userid is missing."; +$content['LN_GROUP_USERHASBEENADDEDGROUP'] = "The User '%1' has been successfully added to group '%2'"; +$content['LN_GROUP_ERRORNOMOREUSERS'] = "There are no more available users who can be added to the group '%1'"; +$content['LN_GROUP_USER_ADD'] = "Add User to the group"; +$content['LN_GROUP_USERDELETE'] = "Remove a User from the group"; +$content['LN_GROUP_ERRORNOUSERSINGROUP'] = "There are no users to remove in this the group '%1'"; +$content['LN_GROUP_ERROR_REMUSERFROMGROUP'] = "The user '%1' could not be removed from the group '%2'"; +$content['LN_GROUP_USERHASBEENREMOVED'] = "The user '%1' has been successfully removed from the group '%2'"; +$content['LN_GROUP_'] = ""; + +// Custom Searches center +$content['LN_SEARCH_CENTER'] = "Custom Searches"; +$content['LN_SEARCH_ADD'] = "Add new Custom Search"; +$content['LN_SEARCH_ID'] = "ID"; +$content['LN_SEARCH_NAME'] = "Search Name"; +$content['LN_SEARCH_QUERY'] = "Search Query"; +$content['LN_SEARCH_TYPE'] = "Assigned to"; +$content['LN_SEARCH_EDIT'] = "Edit Custom Search"; +$content['LN_SEARCH_DELETE'] = "Delete Custom Search"; +$content['LN_SEARCH_ADDEDIT'] = "Add / Edit a Custom Search"; +$content['LN_SEARCH_SELGROUPENABLE'] = ">> Select Group to enable <<"; +$content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; +$content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY'] = "The SearchQuery cannot be empty."; +$content['LN_SEARCH_HASBEENADDED'] = "The Custom Search '%1' has been successfully added."; +$content['LN_SEARCH_ERROR_IDNOTFOUND'] = "Could not find a search with ID '%1'."; +$content['LN_SEARCH_ERROR_INVALIDID'] = "Invalid search ID."; +$content['LN_SEARCH_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; +$content['LN_SEARCH_WARNDELETESEARCH'] = "Are you sure that you want to delete the Custom Search '%1'? This cannot be undone!"; +$content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id '%1' failed!"; +$content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; +$content['LN_SEARCH_'] = ""; + +// Custom Searches center +$content['LN_VIEWS_CENTER'] = "Views Options"; +$content['LN_VIEWS_ID'] = "ID"; +$content['LN_VIEWS_NAME'] = "View Name"; +$content['LN_VIEWS_COLUMNS'] = "View Columns"; +$content['LN_VIEWS_TYPE'] = "Assigned to"; +$content['LN_VIEWS_ADD'] = "Add new View"; +$content['LN_VIEWS_EDIT'] = "Edit View"; +$content['LN_VIEWS_ERROR_IDNOTFOUND'] = "A View with ID '%1' could not be found."; +$content['LN_VIEWS_ERROR_INVALIDID'] = "The View with ID '%1' is not a valid View."; +$content['LN_VIEWS_WARNDELETEVIEW'] = "Are you sure that you want to delete the View '%1'? This cannot be undone!"; +$content['LN_VIEWS_ERROR_DELSEARCH'] = "Deleting of the View with id '%1' failed!"; +$content['LN_VIEWS_ERROR_HASBEENDEL'] = "The View '%1' has been successfully deleted!"; +$content['LN_VIEWS_ADDEDIT'] = "Add / Edit a View"; +$content['LN_VIEWS_COLUMNLIST'] = "Configured Columns"; +$content['LN_VIEWS_ADDCOLUMN'] = "Add Column into list"; +$content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; +$content['LN_VIEWS_COLUMN'] = "Column"; +$content['LN_VIEWS_COLUMN_REMOVE'] = "Remove Column"; +$content['LN_VIEWS_HASBEENADDED'] = "The Custom View '%1' has been successfully added."; +$content['LN_VIEWS_ERROR_NOCOLUMNS'] = "You need to add at least one column in order to add a new Custom View."; +$content['LN_VIEWS_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; +$content['LN_VIEWS_'] = ""; + +$content['LN_SOURCES_CENTER'] = "Sources Options"; +$content['LN_SOURCES_EDIT'] = "Edit Source"; +$content['LN_SOURCES_DELETE'] = "Delete Source"; +$content['LN_SOURCES_ID'] = "ID"; +$content['LN_SOURCES_NAME'] = "Source Name"; +$content['LN_SOURCES_TYPE'] = "Source Type"; +$content['LN_SOURCES_ASSIGNTO'] = "Assigned To"; +$content['LN_SOURCES_DISK'] = "Diskfile"; +$content['LN_SOURCES_DB'] = "MySQL Database"; +$content['LN_SOURCES_PDO'] = "PDO Datasource"; +$content['LN_SOURCES_ADD'] = "Add new Source"; +$content['LN_SOURCES_ADDEDIT'] = "Add / Edit a Source"; +$content['LN_SOURCES_TYPE'] = "Source Type"; +$content['LN_SOURCES_DISKTYPEOPTIONS'] = "Diskfile related Options"; +$content['LN_SOURCES_ERROR_MISSINGPARAM'] = "The paramater '%1' is missing."; +$content['LN_SOURCES_ERROR_NOTAVALIDFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it"; +$content['LN_SOURCES_ERROR_UNKNOWNSOURCE'] = "Unknown Source '%1' detected"; +$content['LN_SOURCE_HASBEENADDED'] = "The new Source '%1' has been successfully added."; +$content['LN_SOURCES_EDIT'] = "Edit Source"; +$content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID'] = "The Source-ID is invalid or could not be found."; +$content['LN_SOURCES_ERROR_IDNOTFOUND'] = "The Source-ID could not be found in the database."; +$content['LN_SOURCES_HASBEENEDIT'] = "The Source '%1' has been successfully edited."; +$content['LN_SOURCES_WARNDELETESEARCH'] = "Are you sure that you want to delete the Source '%1'? This cannot be undone!"; +$content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' failed!"; +$content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; +$content['LN_SOURCES_'] = ""; + + +?> \ No newline at end of file diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 1f9ed04..50ed8bb 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -56,6 +56,7 @@ $content['LN_GEN_SOURCE_DB'] = "Datenbank"; $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Select View"; + $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; @@ -172,4 +173,77 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; + // Install Site + $content['LN_INSTALL_TITLETOP'] = "Installing phpLogCon Version %1 - Step %2"; + $content['LN_INSTALL_TITLE'] = "Installer Step %1"; + $content['LN_INSTALL_ERRORINSTALLED'] = 'phpLogCon is already configured!

If you want to reconfigure phpLogCon, either delete the current config.php or replace it with an empty file.

Click here to return to pgpLogCon start page.'; + $content['LN_INSTALL_FILEORDIRNOTWRITEABLE'] = "At least one file or directory (or more) is not writeable, please check the file permissions (chmod 666)!"; + $content['LN_INSTALL_SAMPLECONFIGMISSING'] = "The sample configuration file '%1' is missing. You have not fully uploaded phplogcon."; + $content['LN_INSTALL_ERRORCONNECTFAILED'] = "Database connect to '%1' failed! Please check Servername, Port, User and Password!"; + $content['LN_INSTALL_ERRORACCESSDENIED'] = "Cannot use the database '%1'! If the database does not exists, create it or check user access permissions!"; + $content['LN_INSTALL_ERRORINVALIDDBFILE'] = "Error, invalid Database definition file (to short!), the file name is '%1'! Please check if the file was correctly uploaded."; + $content['LN_INSTALL_ERRORINSQLCOMMANDS'] = "Error, invalid Database definition file (no sql statements found!), the file name is '%1'!
Please check if the file was not correctly uploaded, or contact the phpLogCon forums for assistance!"; + $content['LN_INSTALL_MISSINGUSERNAME'] = "Username needs to be specified"; + $content['LN_INSTALL_PASSWORDNOTMATCH'] = "Either the password does not match or is to short!"; + $content['LN_INSTALL_FAILEDTOOPENSYSLOGFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it
"; + $content['LN_INSTALL_FAILEDCREATECFGFILE'] = "Coult not create the configuration file in '%1'! Please verify the file permissions!"; + $content['LN_INSTALL_FAILEDREADINGFILE'] = "Error reading the file '%1'! Please verify if the file exists!"; + $content['LN_INSTALL_ERRORREADINGDBFILE'] = "Error reading the default database definition file in '%1'! Please verify if the file exists!"; + $content['LN_INSTALL_STEP1'] = "Step 1 - Prerequisites"; + $content['LN_INSTALL_STEP2'] = "Step 2 - Verify File Permissions"; + $content['LN_INSTALL_STEP3'] = "Step 3 - Basic Configuration"; + $content['LN_INSTALL_STEP4'] = "Step 4 - Create Tables"; + $content['LN_INSTALL_STEP5'] = "Step 5 - Check SQL Results"; + $content['LN_INSTALL_STEP6'] = "Step 6 - Creating the Main Useraccount"; + $content['LN_INSTALL_STEP7'] = "Step 7 - Create the first source for syslog messages"; + $content['LN_INSTALL_STEP8'] = "Step 8 - Done"; + $content['LN_INSTALL_STEP1_TEXT'] = 'Before you start installing phpLogCon, the Installer setup has to check a few things first.
You may have to correct some file permissions first.

Click on to start the Test!'; + $content['LN_INSTALL_STEP2_TEXT'] = "The following file permissions have been checked. Verify the results below!
You may use the configure.sh script from the contrib folder to set the permissions for you."; + $content['LN_INSTALL_STEP3_TEXT'] = "In this step, you configure the basic configurations for phpLogCon."; + $content['LN_INSTALL_STEP4_TEXT'] = 'If you reached this step, the database connection has been successfully verified!

The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database.

Click on to start the creation of the tables'; + $content['LN_INSTALL_STEP5_TEXT'] = "Tables have been created. Check the List below for possible Error's"; + $content['LN_INSTALL_STEP6_TEXT'] = "You are now about to create the initial phpLogCon User Account.
This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center!"; + $content['LN_INSTALL_STEP8_TEXT'] = 'Congratulations! You have successfully installed phpLogCon :)!

Click here to go to your installation.'; + $content['LN_INSTALL_PROGRESS'] = "Install Progress: "; + $content['LN_INSTALL_FRONTEND'] = "Frontend Options"; + $content['LN_INSTALL_NUMOFSYSLOGS'] = "Number of syslog messages per page"; + $content['LN_INSTALL_MSGCHARLIMIT'] = "Message character limit for the main view"; + $content['LN_INSTALL_SHOWDETAILPOP'] = "Show message details popup"; + $content['LN_INSTALL_AUTORESOLVIP'] = "Automatically resolved IP Addresses (inline)"; + $content['LN_INSTALL_USERDBOPTIONS'] = "User Database Options"; + $content['LN_INSTALL_ENABLEUSERDB'] = "Enable User Database"; + $content['LN_INSTALL_SUCCESSSTATEMENTS'] = "Successfully executed statements:"; + $content['LN_INSTALL_FAILEDSTATEMENTS'] = "Failed statements:"; + $content['LN_INSTALL_STEP5_TEXT_NEXT'] = "You can now proceed to the next step adding the first phpLogCon Admin User!"; + $content['LN_INSTALL_STEP5_TEXT_FAILED'] = "At least one statement failed,see error reasons below"; + $content['LN_INSTALL_ERRORMSG'] = "Error Message"; + $content['LN_INSTALL_SQLSTATEMENT'] = "SQL Statement"; + $content['LN_INSTALL_CREATEUSER'] = "Create User Account"; + $content['LN_INSTALL_PASSWORD'] = "Password"; + $content['LN_INSTALL_PASSWORDREPEAT'] = "Repeat Password"; + $content['LN_INSTALL_SUCCESSCREATED'] = "Successfully created User"; + $content['LN_INSTALL_RECHECK'] = "ReCheck"; + $content['LN_INSTALL_FINISH'] = "Finish!"; + $content['LN_INSTALL_'] = ""; + + // Converter Site + $content['LN_CONVERT_TITLE'] = "Configuration Converter Step %1"; + $content['LN_CONVERT_NOTALLOWED'] = "Login"; + $content['LN_CONVERT_ERRORINSTALLED'] = 'phpLogCon is not allowed to convert your settings into the user database.

If you want to convert your convert your settings, add the variable following into your config.php:
$CFG[\'UserDBConvertAllowed\'] = true;

Click here to return to pgpLogCon start page.'; + $content['LN_CONVERT_STEP1'] = "Step 1 - Informations"; + $content['LN_CONVERT_STEP2'] = "Step 2 - Create Tables"; + $content['LN_CONVERT_STEP3'] = "Step 3 - Check SQL Results"; + $content['LN_CONVERT_STEP4'] = "Step 4 - Creating the Main Useraccount"; + $content['LN_CONVERT_STEP5'] = "Step 5 - Import Settings into UserDB"; + $content['LN_CONVERT_TITLETOP'] = "Converting phpLogCon configuration settings - Step "; + $content['LN_CONVERT_STEP1_TEXT'] = 'This script allows you to import your existing configuration from the config.php file. This includes frontend settings, data sources, custom views and custom searches. Do only perform this conversion if you did install phpLogCon without the UserDB System, and decided to enable it now.

ANY EXISTING INSTANCE OF A USERDB WILL BE OVERWRITTEN!

to start the first conversion step!'; + $content['LN_CONVERT_STEP2_TEXT'] = 'The database connection has been successfully verified!

The next step will be to create the necessary database tables for the phpLogCon User System. This might take a while!
WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN!
Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database.

Click on to start the creation of the tables'; + $content['LN_CONVERT_STEP5_TEXT'] = ' to start the last step of the conversion. In this step, your existing configuration from the config.php will be imported into the database.'; + $content['LN_CONVERT_STEP6'] = "Step 8 - Done"; + $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; + $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; + $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; + $content['LN_CONVERT_'] = ""; + $content['LN_CONVERT_'] = ""; + $content['LN_CONVERT_'] = ""; ?> \ No newline at end of file diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 364021f..a3391a6 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -155,7 +155,6 @@ $content['LN_SEARCH_WARNDELETESEARCH'] = "Are you sure that you want to delete t $content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id '%1' failed!"; $content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; $content['LN_SEARCH_'] = ""; -$content['LN_SEARCH_'] = ""; // Custom Searches center $content['LN_VIEWS_CENTER'] = "Views Options"; @@ -180,7 +179,6 @@ $content['LN_VIEWS_HASBEENADDED'] = "The Custom View '%1' has been successfully $content['LN_VIEWS_ERROR_NOCOLUMNS'] = "You need to add at least one column in order to add a new Custom View."; $content['LN_VIEWS_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; $content['LN_VIEWS_'] = ""; -$content['LN_VIEWS_'] = ""; $content['LN_SOURCES_CENTER'] = "Sources Options"; $content['LN_SOURCES_EDIT'] = "Edit Source"; @@ -208,7 +206,6 @@ $content['LN_SOURCES_WARNDELETESEARCH'] = "Are you sure that you want to delete $content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' failed!"; $content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; $content['LN_SOURCES_'] = ""; -$content['LN_SOURCES_'] = ""; ?> \ No newline at end of file diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php new file mode 100644 index 0000000..a3391a6 --- /dev/null +++ b/src/lang/pt_BR/admin.php @@ -0,0 +1,211 @@ + www.phplogcon.org <- + * ----------------------------------------------------------------- + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ +global $content; + +// Global Stuff +$content['LN_ADMINMENU_HOMEPAGE'] = "Back to Show Events"; +$content['LN_ADMINMENU_GENOPT'] = "General Options"; +$content['LN_ADMINMENU_SOURCEOPT'] = "Sources Options"; +$content['LN_ADMINMENU_VIEWSOPT'] = "Views Options"; +$content['LN_ADMINMENU_SEARCHOPT'] = "Search Options"; +$content['LN_ADMINMENU_USEROPT'] = "User Options"; +$content['LN_ADMINMENU_GROUPOPT'] = "Group Options"; +$content['LN_ADMIN_CENTER'] = "Admin center"; +$content['LN_ADMIN_UNKNOWNSTATE'] = "Unknown State"; +$content['LN_ADMIN_ERROR_NOTALLOWED'] = "You are not allowed to access this page with your user level."; +$content['LN_DELETEYES'] = "Yes"; +$content['LN_DELETENO'] = "No"; +$content['LN_GEN_ACTIONS'] = "Available Actions"; +$content['LN_ADMIN_SEND'] = "Send changes"; +$content['LN_GEN_USERONLY'] = "User only"; +$content['LN_GEN_GROUPONLY'] = "Group only"; +$content['LN_GEN_GLOBAL'] = "Global"; +$content['LN_GEN_USERONLY_LONG'] = "For me only
(Only available to your user)"; +$content['LN_GEN_GROUPONLY_LONG'] = "For this group
(Only available to the selected group)"; +$content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; + + +// General Options +$content['LN_ADMIN_GLOBFRONTEND'] = "Global frontend options"; +$content['LN_ADMIN_USERFRONTEND'] = "User specific frontend options"; +$content['LN_ADMIN_MISC'] = "Miscellaneous Options"; +$content['LN_GEN_SHOWDEBUGMSG'] = "Show Debug messages"; +$content['LN_GEN_DEBUGGRIDCOUNTER'] = "Show Debug Gridcounter"; +$content['LN_GEN_SHOWPAGERENDERSTATS'] = "Show Pagerenderstats"; +$content['LN_GEN_ENABLEGZIP'] = "Enable GZIP Compressed Output"; +$content['LN_GEN_DEBUGUSERLOGIN'] = "Debug Userlogin"; +$content['LN_GEN_WEBSTYLE'] = "Default selected style"; +$content['LN_GEN_SELLANGUAGE'] = "Default selected language"; +$content['LN_GEN_PREPENDTITLE'] = "Prepend this string in title"; +$content['LN_GEN_USETODAY'] = "Use Today and Yesterday in timefields"; +$content['LN_GEN_DETAILPOPUPS'] = "Use Popup to display the full messagedetails"; +$content['LN_GEN_MSGCHARLIMIT'] = "Character limit of the message in main view"; +$content['LN_GEN_ENTRIESPERPAGE'] = "Number of entries per page"; +$content['LN_GEN_AUTORELOADSECONDS'] = "Enable autoreload after seconds"; +$content['LN_GEN_IPADRRESOLVE'] = "Resolve IP Addresses using DNS"; +$content['LN_GEN_CUSTBTNCAPT'] = "Custom search caption"; +$content['LN_GEN_CUSTBTNSRCH'] = "Custom search string"; +$content['LN_GEN_SUCCESSFULLYSAVED'] = "The configuration Values have been successfully saved"; +$content['LN_GEN_INTERNAL'] = "Internal"; +$content['LN_GEN_DISABLED'] = "Function disabled"; +$content['LN_GEN_CONFIGFILE'] = "Configuration File"; +$content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; + +// User Center +$content['LN_USER_CENTER'] = "User Options"; +$content['LN_USER_ID'] = "ID"; +$content['LN_USER_NAME'] = "Username"; +$content['LN_USER_ADD'] = "Add User"; +$content['LN_USER_EDIT'] = "Edit User"; +$content['LN_USER_DELETE'] = "Delete User"; +$content['LN_USER_PASSWORD1'] = "Password"; +$content['LN_USER_PASSWORD2'] = "Confirm Password"; +$content['LN_USER_ERROR_IDNOTFOUND'] = "Error, User with ID '%1' , was not found"; +$content['LN_USER_ERROR_DONOTDELURSLF'] = "Error, you can not DELETE YOURSELF!"; +$content['LN_USER_ERROR_DELUSER'] = "Deleting of the user with id '%1' failed!"; +$content['LN_USER_ERROR_INVALIDID'] = "Error, invalid ID, User not found"; +$content['LN_USER_ERROR_HASBEENDEL'] = "The User '%1' has been successfully deleted!"; +$content['LN_USER_ERROR_USEREMPTY'] = "Error, Username was empty"; +$content['LN_USER_ERROR_USERNAMETAKEN'] = "Error, this Username is already taken!"; +$content['LN_USER_ERROR_PASSSHORT'] = "Error, Password was to short, or did not match"; +$content['LN_USER_ERROR_HASBEENADDED'] = "User '%1' has been successfully added"; +$content['LN_USER_ERROR_HASBEENEDIT'] = "User '%1' has been successfully edited"; +$content['LN_USER_ISADMIN'] = "Is Admin?"; +$content['LN_USER_ADDEDIT'] = "Add/Edit User"; +$content['LN_USER_WARNREMOVEADMIN'] = "You are about to revoke your own administrative priviledges. Are you sure to remove your admin status?"; +$content['LN_USER_WARNDELETEUSER'] = "Are you sure that you want to delete the User '%1'? All his personal settings will be deleted as well."; +$content['LN_USER_ERROR_INVALIDSESSIONS'] = "Invalid User Session."; +$content['LN_USER_'] = ""; + +// Group center +$content['LN_GROUP_CENTER'] = "Group Center"; +$content['LN_GROUP_ID'] = "ID"; +$content['LN_GROUP_NAME'] = "Groupname"; +$content['LN_GROUP_DESCRIPTION'] = "Groupdescription"; +$content['LN_GROUP_TYPE'] = "Grouptype"; +$content['LN_GROUP_ADD'] = "Add Group"; +$content['LN_GROUP_EDIT'] = "Edit Group"; +$content['LN_GROUP_DELETE'] = "Delete Group"; +$content['LN_GROUP_NOGROUPS'] = "No groups have been added yet"; +$content['LN_GROUP_ADDEDIT'] = "Add/Edit Group"; +$content['LN_GROUP_ERROR_GROUPEMPTY'] = "The groupname cannot be empty."; +$content['LN_GROUP_ERROR_GROUPNAMETAKEN'] = "The groupname has already been taken."; +$content['LN_GROUP_HASBEENADDED'] = "The group '%1' has been successfully added."; +$content['LN_GROUP_ERROR_IDNOTFOUND'] = "The group with ID '%1' could not be found."; +$content['LN_GROUP_ERROR_HASBEENEDIT'] = "The group '%1' has been successfully edited."; +$content['LN_GROUP_ERROR_INVALIDGROUP'] = "Error, invalid ID, Group not found"; +$content['LN_GROUP_WARNDELETEGROUP'] = "Are you sure that you want to delete the Group '%1'? All Groupsettings will be deleted as well."; +$content['LN_GROUP_ERROR_DELGROUP'] = "Deleting of the group with id '%1' failed!"; +$content['LN_GROUP_ERROR_HASBEENDEL'] = "The Group '%1' has been successfully deleted!"; +$content['LN_GROUP_MEMBERS'] = "Groupmembers: "; +$content['LN_GROUP_ADDUSER'] = "Add User to Group"; +$content['LN_GROUP_ERROR_USERIDMISSING'] = "The userid is missing."; +$content['LN_GROUP_USERHASBEENADDEDGROUP'] = "The User '%1' has been successfully added to group '%2'"; +$content['LN_GROUP_ERRORNOMOREUSERS'] = "There are no more available users who can be added to the group '%1'"; +$content['LN_GROUP_USER_ADD'] = "Add User to the group"; +$content['LN_GROUP_USERDELETE'] = "Remove a User from the group"; +$content['LN_GROUP_ERRORNOUSERSINGROUP'] = "There are no users to remove in this the group '%1'"; +$content['LN_GROUP_ERROR_REMUSERFROMGROUP'] = "The user '%1' could not be removed from the group '%2'"; +$content['LN_GROUP_USERHASBEENREMOVED'] = "The user '%1' has been successfully removed from the group '%2'"; +$content['LN_GROUP_'] = ""; + +// Custom Searches center +$content['LN_SEARCH_CENTER'] = "Custom Searches"; +$content['LN_SEARCH_ADD'] = "Add new Custom Search"; +$content['LN_SEARCH_ID'] = "ID"; +$content['LN_SEARCH_NAME'] = "Search Name"; +$content['LN_SEARCH_QUERY'] = "Search Query"; +$content['LN_SEARCH_TYPE'] = "Assigned to"; +$content['LN_SEARCH_EDIT'] = "Edit Custom Search"; +$content['LN_SEARCH_DELETE'] = "Delete Custom Search"; +$content['LN_SEARCH_ADDEDIT'] = "Add / Edit a Custom Search"; +$content['LN_SEARCH_SELGROUPENABLE'] = ">> Select Group to enable <<"; +$content['LN_SEARCH_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; +$content['LN_SEARCH_ERROR_SEARCHQUERYEMPTY'] = "The SearchQuery cannot be empty."; +$content['LN_SEARCH_HASBEENADDED'] = "The Custom Search '%1' has been successfully added."; +$content['LN_SEARCH_ERROR_IDNOTFOUND'] = "Could not find a search with ID '%1'."; +$content['LN_SEARCH_ERROR_INVALIDID'] = "Invalid search ID."; +$content['LN_SEARCH_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; +$content['LN_SEARCH_WARNDELETESEARCH'] = "Are you sure that you want to delete the Custom Search '%1'? This cannot be undone!"; +$content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id '%1' failed!"; +$content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; +$content['LN_SEARCH_'] = ""; + +// Custom Searches center +$content['LN_VIEWS_CENTER'] = "Views Options"; +$content['LN_VIEWS_ID'] = "ID"; +$content['LN_VIEWS_NAME'] = "View Name"; +$content['LN_VIEWS_COLUMNS'] = "View Columns"; +$content['LN_VIEWS_TYPE'] = "Assigned to"; +$content['LN_VIEWS_ADD'] = "Add new View"; +$content['LN_VIEWS_EDIT'] = "Edit View"; +$content['LN_VIEWS_ERROR_IDNOTFOUND'] = "A View with ID '%1' could not be found."; +$content['LN_VIEWS_ERROR_INVALIDID'] = "The View with ID '%1' is not a valid View."; +$content['LN_VIEWS_WARNDELETEVIEW'] = "Are you sure that you want to delete the View '%1'? This cannot be undone!"; +$content['LN_VIEWS_ERROR_DELSEARCH'] = "Deleting of the View with id '%1' failed!"; +$content['LN_VIEWS_ERROR_HASBEENDEL'] = "The View '%1' has been successfully deleted!"; +$content['LN_VIEWS_ADDEDIT'] = "Add / Edit a View"; +$content['LN_VIEWS_COLUMNLIST'] = "Configured Columns"; +$content['LN_VIEWS_ADDCOLUMN'] = "Add Column into list"; +$content['LN_VIEWS_ERROR_DISPLAYNAMEEMPTY'] = "The DisplayName cannot be empty."; +$content['LN_VIEWS_COLUMN'] = "Column"; +$content['LN_VIEWS_COLUMN_REMOVE'] = "Remove Column"; +$content['LN_VIEWS_HASBEENADDED'] = "The Custom View '%1' has been successfully added."; +$content['LN_VIEWS_ERROR_NOCOLUMNS'] = "You need to add at least one column in order to add a new Custom View."; +$content['LN_VIEWS_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; +$content['LN_VIEWS_'] = ""; + +$content['LN_SOURCES_CENTER'] = "Sources Options"; +$content['LN_SOURCES_EDIT'] = "Edit Source"; +$content['LN_SOURCES_DELETE'] = "Delete Source"; +$content['LN_SOURCES_ID'] = "ID"; +$content['LN_SOURCES_NAME'] = "Source Name"; +$content['LN_SOURCES_TYPE'] = "Source Type"; +$content['LN_SOURCES_ASSIGNTO'] = "Assigned To"; +$content['LN_SOURCES_DISK'] = "Diskfile"; +$content['LN_SOURCES_DB'] = "MySQL Database"; +$content['LN_SOURCES_PDO'] = "PDO Datasource"; +$content['LN_SOURCES_ADD'] = "Add new Source"; +$content['LN_SOURCES_ADDEDIT'] = "Add / Edit a Source"; +$content['LN_SOURCES_TYPE'] = "Source Type"; +$content['LN_SOURCES_DISKTYPEOPTIONS'] = "Diskfile related Options"; +$content['LN_SOURCES_ERROR_MISSINGPARAM'] = "The paramater '%1' is missing."; +$content['LN_SOURCES_ERROR_NOTAVALIDFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it"; +$content['LN_SOURCES_ERROR_UNKNOWNSOURCE'] = "Unknown Source '%1' detected"; +$content['LN_SOURCE_HASBEENADDED'] = "The new Source '%1' has been successfully added."; +$content['LN_SOURCES_EDIT'] = "Edit Source"; +$content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID'] = "The Source-ID is invalid or could not be found."; +$content['LN_SOURCES_ERROR_IDNOTFOUND'] = "The Source-ID could not be found in the database."; +$content['LN_SOURCES_HASBEENEDIT'] = "The Source '%1' has been successfully edited."; +$content['LN_SOURCES_WARNDELETESEARCH'] = "Are you sure that you want to delete the Source '%1'? This cannot be undone!"; +$content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' failed!"; +$content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; +$content['LN_SOURCES_'] = ""; + + +?> \ No newline at end of file diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 167efc3..0c869c3 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -61,6 +61,7 @@ $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_SELECTVIEW'] = "Visão"; $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; + $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; @@ -177,4 +178,77 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_LOGIN_ERRWRONGPASSWORD'] = "Wrong username or password!"; $content['LN_LOGIN_USERPASSMISSING'] = "Username or password not given"; + // Install Site + $content['LN_INSTALL_TITLETOP'] = "Installing phpLogCon Version %1 - Step %2"; + $content['LN_INSTALL_TITLE'] = "Installer Step %1"; + $content['LN_INSTALL_ERRORINSTALLED'] = 'phpLogCon is already configured!

If you want to reconfigure phpLogCon, either delete the current config.php or replace it with an empty file.

Click here to return to pgpLogCon start page.'; + $content['LN_INSTALL_FILEORDIRNOTWRITEABLE'] = "At least one file or directory (or more) is not writeable, please check the file permissions (chmod 666)!"; + $content['LN_INSTALL_SAMPLECONFIGMISSING'] = "The sample configuration file '%1' is missing. You have not fully uploaded phplogcon."; + $content['LN_INSTALL_ERRORCONNECTFAILED'] = "Database connect to '%1' failed! Please check Servername, Port, User and Password!"; + $content['LN_INSTALL_ERRORACCESSDENIED'] = "Cannot use the database '%1'! If the database does not exists, create it or check user access permissions!"; + $content['LN_INSTALL_ERRORINVALIDDBFILE'] = "Error, invalid Database definition file (to short!), the file name is '%1'! Please check if the file was correctly uploaded."; + $content['LN_INSTALL_ERRORINSQLCOMMANDS'] = "Error, invalid Database definition file (no sql statements found!), the file name is '%1'!
Please check if the file was not correctly uploaded, or contact the phpLogCon forums for assistance!"; + $content['LN_INSTALL_MISSINGUSERNAME'] = "Username needs to be specified"; + $content['LN_INSTALL_PASSWORDNOTMATCH'] = "Either the password does not match or is to short!"; + $content['LN_INSTALL_FAILEDTOOPENSYSLOGFILE'] = "Failed to open the syslog file '%1'! Check if the file exists and phplogcon has sufficient rights to it
"; + $content['LN_INSTALL_FAILEDCREATECFGFILE'] = "Coult not create the configuration file in '%1'! Please verify the file permissions!"; + $content['LN_INSTALL_FAILEDREADINGFILE'] = "Error reading the file '%1'! Please verify if the file exists!"; + $content['LN_INSTALL_ERRORREADINGDBFILE'] = "Error reading the default database definition file in '%1'! Please verify if the file exists!"; + $content['LN_INSTALL_STEP1'] = "Step 1 - Prerequisites"; + $content['LN_INSTALL_STEP2'] = "Step 2 - Verify File Permissions"; + $content['LN_INSTALL_STEP3'] = "Step 3 - Basic Configuration"; + $content['LN_INSTALL_STEP4'] = "Step 4 - Create Tables"; + $content['LN_INSTALL_STEP5'] = "Step 5 - Check SQL Results"; + $content['LN_INSTALL_STEP6'] = "Step 6 - Creating the Main Useraccount"; + $content['LN_INSTALL_STEP7'] = "Step 7 - Create the first source for syslog messages"; + $content['LN_INSTALL_STEP8'] = "Step 8 - Done"; + $content['LN_INSTALL_STEP1_TEXT'] = 'Before you start installing phpLogCon, the Installer setup has to check a few things first.
You may have to correct some file permissions first.

Click on to start the Test!'; + $content['LN_INSTALL_STEP2_TEXT'] = "The following file permissions have been checked. Verify the results below!
You may use the configure.sh script from the contrib folder to set the permissions for you."; + $content['LN_INSTALL_STEP3_TEXT'] = "In this step, you configure the basic configurations for phpLogCon."; + $content['LN_INSTALL_STEP4_TEXT'] = 'If you reached this step, the database connection has been successfully verified!

The next step will be to create the necessary database tables used by the phpLogCon User System. This might take a while!
WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN! Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database.

Click on to start the creation of the tables'; + $content['LN_INSTALL_STEP5_TEXT'] = "Tables have been created. Check the List below for possible Error's"; + $content['LN_INSTALL_STEP6_TEXT'] = "You are now about to create the initial phpLogCon User Account.
This will be the first administrative user, which will be needed to login into phpLogCon and access the Admin Center!"; + $content['LN_INSTALL_STEP8_TEXT'] = 'Congratulations! You have successfully installed phpLogCon :)!

Click here to go to your installation.'; + $content['LN_INSTALL_PROGRESS'] = "Install Progress: "; + $content['LN_INSTALL_FRONTEND'] = "Frontend Options"; + $content['LN_INSTALL_NUMOFSYSLOGS'] = "Number of syslog messages per page"; + $content['LN_INSTALL_MSGCHARLIMIT'] = "Message character limit for the main view"; + $content['LN_INSTALL_SHOWDETAILPOP'] = "Show message details popup"; + $content['LN_INSTALL_AUTORESOLVIP'] = "Automatically resolved IP Addresses (inline)"; + $content['LN_INSTALL_USERDBOPTIONS'] = "User Database Options"; + $content['LN_INSTALL_ENABLEUSERDB'] = "Enable User Database"; + $content['LN_INSTALL_SUCCESSSTATEMENTS'] = "Successfully executed statements:"; + $content['LN_INSTALL_FAILEDSTATEMENTS'] = "Failed statements:"; + $content['LN_INSTALL_STEP5_TEXT_NEXT'] = "You can now proceed to the next step adding the first phpLogCon Admin User!"; + $content['LN_INSTALL_STEP5_TEXT_FAILED'] = "At least one statement failed,see error reasons below"; + $content['LN_INSTALL_ERRORMSG'] = "Error Message"; + $content['LN_INSTALL_SQLSTATEMENT'] = "SQL Statement"; + $content['LN_INSTALL_CREATEUSER'] = "Create User Account"; + $content['LN_INSTALL_PASSWORD'] = "Password"; + $content['LN_INSTALL_PASSWORDREPEAT'] = "Repeat Password"; + $content['LN_INSTALL_SUCCESSCREATED'] = "Successfully created User"; + $content['LN_INSTALL_RECHECK'] = "ReCheck"; + $content['LN_INSTALL_FINISH'] = "Finish!"; + $content['LN_INSTALL_'] = ""; + + // Converter Site + $content['LN_CONVERT_TITLE'] = "Configuration Converter Step %1"; + $content['LN_CONVERT_NOTALLOWED'] = "Login"; + $content['LN_CONVERT_ERRORINSTALLED'] = 'phpLogCon is not allowed to convert your settings into the user database.

If you want to convert your convert your settings, add the variable following into your config.php:
$CFG[\'UserDBConvertAllowed\'] = true;

Click here to return to pgpLogCon start page.'; + $content['LN_CONVERT_STEP1'] = "Step 1 - Informations"; + $content['LN_CONVERT_STEP2'] = "Step 2 - Create Tables"; + $content['LN_CONVERT_STEP3'] = "Step 3 - Check SQL Results"; + $content['LN_CONVERT_STEP4'] = "Step 4 - Creating the Main Useraccount"; + $content['LN_CONVERT_STEP5'] = "Step 5 - Import Settings into UserDB"; + $content['LN_CONVERT_TITLETOP'] = "Converting phpLogCon configuration settings - Step "; + $content['LN_CONVERT_STEP1_TEXT'] = 'This script allows you to import your existing configuration from the config.php file. This includes frontend settings, data sources, custom views and custom searches. Do only perform this conversion if you did install phpLogCon without the UserDB System, and decided to enable it now.

ANY EXISTING INSTANCE OF A USERDB WILL BE OVERWRITTEN!

to start the first conversion step!'; + $content['LN_CONVERT_STEP2_TEXT'] = 'The database connection has been successfully verified!

The next step will be to create the necessary database tables for the phpLogCon User System. This might take a while!
WARNING, if you have an existing phpLogCon installation in this database with the same tableprefix, all your data will be OVERWRITTEN!
Make sure you are using a fresh database, or you want to overwrite your old phpLogCon database.

Click on to start the creation of the tables'; + $content['LN_CONVERT_STEP5_TEXT'] = ' to start the last step of the conversion. In this step, your existing configuration from the config.php will be imported into the database.'; + $content['LN_CONVERT_STEP6'] = "Step 8 - Done"; + $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; + $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; + $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; + $content['LN_CONVERT_'] = ""; + $content['LN_CONVERT_'] = ""; + $content['LN_CONVERT_'] = ""; ?> \ No newline at end of file diff --git a/src/login.php b/src/login.php index 81b811b..75180b1 100644 --- a/src/login.php +++ b/src/login.php @@ -1,111 +1,111 @@ - File to login users in PhpLogCon - * - * All directives are explained within this file - * - * Copyright (C) 2008 Adiscon GmbH. - * - * This file is part of phpLogCon. - * - * PhpLogCon is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PhpLogCon is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with phpLogCon. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this - * distribution - ********************************************************************* -*/ - -// *** Default includes and procedures *** // -define('IN_PHPLOGCON', true); -$gl_root_path = './'; - -// Now include necessary include files! -include($gl_root_path . 'include/functions_common.php'); -include($gl_root_path . 'include/functions_frontendhelpers.php'); -//include($gl_root_path . 'include/functions_filters.php'); - -// To avoid infinite redirects! -define('IS_NOLOGINPAGE', true); -$content['IS_NOLOGINPAGE'] = true; -InitPhpLogCon(); -// --- // - -// --- BEGIN Custom Code - -// Set Defaults -$content['uname'] = ""; -$content['pass'] = ""; - -// Set Referer -if ( isset($_GET['referer']) ) - $szRedir = $_GET['referer']; -else if ( isset($_POST['referer']) ) - $szRedir = $_POST['referer']; -else - $szRedir = "index.php"; // Default - -if ( isset($_POST['op']) && $_POST['op'] == "login" ) -{ - // Perform login! - if ( $_POST['op'] == "login" ) - { - if ( - (isset($_POST['uname']) && strlen($_POST['uname']) > 0) - && - (isset($_POST['pass']) && strlen($_POST['pass']) > 0) - ) - { - // Set Username and password - $content['uname'] = DB_RemoveBadChars($_POST['uname']); - $content['pass'] = DB_RemoveBadChars($_POST['pass']); - - if ( !CheckUserLogin( $content['uname'], $content['pass']) ) - { - $content['ISERROR'] = "true"; - $content['ERROR_MSG'] = $content['LN_LOGIN_ERRWRONGPASSWORD']; - } - else - RedirectPage( urldecode($szRedir) ); - } - else - { - $content['ISERROR'] = "true"; - $content['ERROR_MSG'] = $content['LN_LOGIN_USERPASSMISSING']; - } - } -} -else if ( isset($_GET['op']) && $_GET['op'] == "logoff" ) -{ - // logoff in this case - DoLogOff(); -} -// --- END Custom Code - -// --- CONTENT Vars -$content['REDIR_LOGIN'] = $szRedir; -$content['TITLE'] = "phpLogCon - User Login"; // Title of the Page -// --- - -// --- Parsen and Output -InitTemplateParser(); -$page -> parser($content, "login.html"); -$page -> output(); -// --- - + File to login users in PhpLogCon + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +//include($gl_root_path . 'include/functions_filters.php'); + +// To avoid infinite redirects! +define('IS_NOLOGINPAGE', true); +$content['IS_NOLOGINPAGE'] = true; +InitPhpLogCon(); +// --- // + +// --- BEGIN Custom Code + +// Set Defaults +$content['uname'] = ""; +$content['pass'] = ""; + +// Set Referer +if ( isset($_GET['referer']) ) + $szRedir = $_GET['referer']; +else if ( isset($_POST['referer']) ) + $szRedir = $_POST['referer']; +else + $szRedir = "index.php"; // Default + +if ( isset($_POST['op']) && $_POST['op'] == "login" ) +{ + // Perform login! + if ( $_POST['op'] == "login" ) + { + if ( + (isset($_POST['uname']) && strlen($_POST['uname']) > 0) + && + (isset($_POST['pass']) && strlen($_POST['pass']) > 0) + ) + { + // Set Username and password + $content['uname'] = DB_RemoveBadChars($_POST['uname']); + $content['pass'] = DB_RemoveBadChars($_POST['pass']); + + if ( !CheckUserLogin( $content['uname'], $content['pass']) ) + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = $content['LN_LOGIN_ERRWRONGPASSWORD']; + } + else + RedirectPage( urldecode($szRedir) ); + } + else + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = $content['LN_LOGIN_USERPASSMISSING']; + } + } +} +else if ( isset($_GET['op']) && $_GET['op'] == "logoff" ) +{ + // logoff in this case + DoLogOff(); +} +// --- END Custom Code + +// --- CONTENT Vars +$content['REDIR_LOGIN'] = $szRedir; +$content['TITLE'] = "phpLogCon - User Login"; // Title of the Page +// --- + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "login.html"); +$page -> output(); +// --- + ?> \ No newline at end of file From c51fc81f8926526a5ef199bdca3aa06b32b4b6ec Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 24 Jul 2008 15:11:40 +0200 Subject: [PATCH 030/142] Fixed a few minor issues. Added a few missing options in General Options --- src/admin/index.php | 48 ++++++++++++++++++++++------ src/include/functions_db.php | 7 ++-- src/lang/de/admin.php | 2 ++ src/lang/en/admin.php | 3 ++ src/lang/pt_BR/admin.php | 2 ++ src/templates/admin/admin_index.html | 23 +++++++++++++ 6 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index b683632..a4df05b 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -62,8 +62,6 @@ if ( isset($_SESSION['SESSION_ISADMIN']) && $_SESSION['SESSION_ISADMIN'] == 1 ) else $content['EditAllowed'] = false; - - // Check for changes first | Abort if Edit is not allowed if ( isset($_POST['op']) && $content['EditAllowed'] ) { @@ -78,7 +76,13 @@ if ( isset($_POST['op']) && $content['EditAllowed'] ) } // Read default theme - if ( isset ($_POST['ViewDefaultTheme']) ) { $content['ViewDefaultTheme'] = DB_RemoveBadChars($_POST['ViewDefaultTheme']); } + if ( isset ($_POST['ViewDefaultTheme']) ) { $content['ViewDefaultTheme'] = $_POST['ViewDefaultTheme']; } + + // Read default VIEW | Check if View exists as well! + if ( isset ($_POST['DefaultViewsID']) && isset($content['Views'][$_POST['DefaultViewsID']] )) { $content['DefaultViewsID'] = $_POST['DefaultViewsID']; } + + // Read default SOURCES | Check if Source exists as well! + if ( isset ($_POST['DefaultSourceID']) && isset($content['Sources'][$_POST['DefaultSourceID']] )) { $content['DefaultSourceID'] = $_POST['DefaultSourceID']; } // Read checkboxes if ( isset ($_POST['ViewUseTodayYesterday']) ) { $content['ViewUseTodayYesterday'] = 1; } else { $content['ViewUseTodayYesterday'] = 0; } @@ -91,14 +95,14 @@ if ( isset($_POST['op']) && $content['EditAllowed'] ) if ( isset ($_POST['DebugUserLogin']) ) { $content['DebugUserLogin'] = 1; } else { $content['DebugUserLogin'] = 0; } // Read Text number fields - if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = DB_RemoveBadChars($_POST['ViewMessageCharacterLimit']); } - if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = DB_RemoveBadChars($_POST['ViewEntriesPerPage']); } - if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = DB_RemoveBadChars($_POST['ViewEnableAutoReloadSeconds']); } + if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_POST['ViewMessageCharacterLimit']; } + if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = $_POST['ViewEntriesPerPage']; } + if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = $_POST['ViewEnableAutoReloadSeconds']; } // Read Text fields - if ( isset ($_POST['PrependTitle']) ) { $content['PrependTitle'] = DB_RemoveBadChars($_POST['PrependTitle']); } - if ( isset ($_POST['SearchCustomButtonCaption']) ) { $content['SearchCustomButtonCaption'] = DB_RemoveBadChars($_POST['SearchCustomButtonCaption']); } - if ( isset ($_POST['SearchCustomButtonSearch']) ) { $content['SearchCustomButtonSearch'] = DB_RemoveBadChars($_POST['SearchCustomButtonSearch']); } + if ( isset ($_POST['PrependTitle']) ) { $content['PrependTitle'] = $_POST['PrependTitle']; } + if ( isset ($_POST['SearchCustomButtonCaption']) ) { $content['SearchCustomButtonCaption'] = $_POST['SearchCustomButtonCaption']; } + if ( isset ($_POST['SearchCustomButtonSearch']) ) { $content['SearchCustomButtonSearch'] = $_POST['SearchCustomButtonSearch']; } // Save configuration variables now SaveGeneralSettingsIntoDB(); @@ -121,6 +125,32 @@ if ($content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompre if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } // --- +// --- Init for DefaultView field! +// copy Views Array +$content['VIEWS'] = $content['Views']; +if ( !isset($content['DefaultViewsID']) ) { $content['DefaultViewsID'] = 'SYSLOG'; } +foreach ( $content['VIEWS'] as $myView ) +{ + if ( $myView['ID'] == $content['DefaultViewsID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; +} +// --- + +// --- Init for DefaultSource field! +// copy Views Array +$content['SOURCES'] = $content['Sources']; +if ( !isset($content['DefaultSourceID']) ) { $content['DefaultSourceID'] = ''; } +foreach ( $content['SOURCES'] as $myView ) +{ + if ( $myView['ID'] == $content['DefaultSourceID'] ) + $content['SOURCES'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['SOURCES'][ $myView['ID'] ]['selected'] = ""; +} +// --- + // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); $content['TITLE'] .= " :: " . $content['LN_ADMINMENU_GENOPT']; diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 6484251..42e5827 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -304,11 +304,12 @@ function DB_Exec($query) function PrepareValueForDB($szValue) { +//echo "
" . $szValue . "
!" . preg_match("/[^\\\\]['\\\\][^'\\\\]/e", $szValue, $matches) . "
"; // Copy value for DB and check for BadDB Chars! - if ( preg_match("/(?
{LN_GEN_DEFVIEWS} + +
{LN_GEN_DEFSOURCE} + +
{LN_GEN_PREPENDTITLE}
@@ -54,9 +54,9 @@ diff --git a/src/templates/admin/result.html b/src/templates/admin/result.html index 8bacd6a..7238dac 100644 --- a/src/templates/admin/result.html +++ b/src/templates/admin/result.html @@ -1,5 +1,6 @@ +

{LN_GROUP_CENTER}
- - -

-
-

{ERROR_MSG}

-
- -

diff --git a/src/templates/admin/admin_searches.html b/src/templates/admin/admin_searches.html index a3d1769..8154bae 100644 --- a/src/templates/admin/admin_searches.html +++ b/src/templates/admin/admin_searches.html @@ -1,9 +1,12 @@ +
-

{ERROR_MSG}

+

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

+{LN_GEN_ERRORRETURNPREV}
+
diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index b18d0cc..3a33998 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -30,9 +30,12 @@ +
-

{ERROR_MSG}

+

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

+{LN_GEN_ERRORRETURNPREV}
+
diff --git a/src/templates/admin/admin_users.html b/src/templates/admin/admin_users.html index 3ed324e..71e897f 100644 --- a/src/templates/admin/admin_users.html +++ b/src/templates/admin/admin_users.html @@ -1,9 +1,12 @@ +
-

{ERROR_MSG}

+

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

+{LN_GEN_ERRORRETURNPREV}
+
diff --git a/src/templates/admin/admin_views.html b/src/templates/admin/admin_views.html index 61276a1..d302e8a 100644 --- a/src/templates/admin/admin_views.html +++ b/src/templates/admin/admin_views.html @@ -1,9 +1,12 @@ +
-

{ERROR_MSG}

+

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

+{LN_GEN_ERRORRETURNPREV}
+
@@ -57,7 +60,7 @@ - +
diff --git a/src/templates/details.html b/src/templates/details.html index 04bbf0b..eb80a7d 100644 --- a/src/templates/details.html +++ b/src/templates/details.html @@ -88,7 +88,7 @@

-

{LN_ERROR_NORECORDS} (code {error_code} ) - Error Details:

+

{LN_ERROR_NORECORDS} (code {error_code} ) - {LN_GEN_ERRORDETAILS}

{detailederror}

diff --git a/src/templates/index.html b/src/templates/index.html index c63404f..c531566 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -343,7 +343,7 @@

-

{LN_ERROR_NORECORDS} - Error Details:

+

{LN_ERROR_NORECORDS} - {LN_GEN_ERRORDETAILS}

{detailederror}

diff --git a/src/themes/dark/main.css b/src/themes/dark/main.css index 5f32dda..135399f 100644 --- a/src/themes/dark/main.css +++ b/src/themes/dark/main.css @@ -333,9 +333,6 @@ A.cellmenu1_link:hover { font: bold 12px Verdana, Arial, Helvetica, sans-serif; COLOR: #FF2222; - border-top: 1px solid; - border-bottom: 1px solid; - border-color: #58363E; } .PriorityEmergency { diff --git a/src/themes/default/main.css b/src/themes/default/main.css index 44eec8d..770eba7 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -339,9 +339,6 @@ A.cellmenu1_link:hover { font: bold 12px Verdana, Arial, Helvetica, sans-serif; COLOR: #FF0000; - border-top: 1px solid; - border-bottom: 1px solid; - border-color: #58363E; } .PriorityEmergency { From 0ad4650993b758ef2cc1fe6b2e6c07d2156fed64 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 25 Jul 2008 13:34:24 +0200 Subject: [PATCH 035/142] Fixed minor bug reading the correct default view for sources from DB --- src/include/functions_config.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 06f5476..a77179f 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -75,20 +75,30 @@ function InitSourceConfigs() } // --- - // Set different view if necessary + // Set default view id to source + $szDefaultViewID = isset($CFG['DefaultViewsID']) && strlen($CFG['DefaultViewsID']) > 0 ? $CFG['DefaultViewsID'] : "SYSLOG"; + if ( isset($_SESSION[$iSourceID . "-View"]) ) { - // Overwrite configured view! - $content['Sources'][$iSourceID]['ViewID'] = $_SESSION[$iSourceID . "-View"]; + // check if view is valid + $UserSessionViewID = $_SESSION[$iSourceID . "-View"]; + + if ( isset($content['Views'][$UserSessionViewID]) ) + { + // Overwrite configured view! + $content['Sources'][$iSourceID]['ViewID'] = $_SESSION[$iSourceID . "-View"]; + } + else + $content['Sources'][$iSourceID]['ViewID'] = $szDefaultViewID; } else { - if ( isset($mysource['ViewID']) && strlen($mysource['ViewID']) > 0 ) + if ( isset($mysource['ViewID']) && strlen($mysource['ViewID']) > 0 && isset($content['Views'][ $mysource['ViewID'] ]) ) // Set to configured Source ViewID $content['Sources'][$iSourceID]['ViewID'] = $mysource['ViewID']; else // Not configured, maybe old legacy cfg. Set default view. - $content['Sources'][$iSourceID]['ViewID'] = strlen($CFG['DefaultViewsID']) > 0 ? $CFG['DefaultViewsID'] : "SYSLOG"; + $content['Sources'][$iSourceID]['ViewID'] = $szDefaultViewID; } // Only for the display box From e6dc1e4b25933ba81795eef47c36449e033844dd Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 25 Jul 2008 14:25:32 +0200 Subject: [PATCH 036/142] Fixed a bug in the input checking of files when Diskfile source was configured --- src/admin/sources.php | 25 ++++++++++++++++++++++--- src/include/functions_db.php | 9 +++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/admin/sources.php b/src/admin/sources.php index 125ae6d..e95d6de 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -334,10 +334,29 @@ if ( isset($_POST['op']) ) $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SYSLOGFILE'] ); } // Check if file is accessable! - else if ( !is_file($content['SourceDiskFile']) ) + else { - $content['ISERROR'] = true; - $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $content['SourceDiskFile'] ); + // Get plain filename for testing! + $szFileName = DB_StripSlahes($content['SourceDiskFile']); + + // Take as it is if rootpath! + if ( + ( ($pos = strpos($szFileName, "/")) !== FALSE && $pos == 0) || + ( ($pos = strpos($szFileName, ":\\")) !== FALSE ) || + ( ($pos = strpos($szFileName, ":/")) !== FALSE ) + ) + { + // Nothing really todo + $szFileName = $szFileName; + } + else // prepend basepath! + $szFileName = $gl_root_path . $szFileName; + + if ( !is_file($szFileName) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $szFileName ); + } } } // DB Params diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 42e5827..7aad19f 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -248,6 +248,15 @@ function DB_RemoveBadChars($myString) */ } +function DB_StripSlahes($myString) +{ + // Replace with internal PHP Functions! + if ( !get_magic_quotes_runtime() ) + return stripslashes($myString); + else + return $myString; +} + function DB_ReturnLastInsertID($myResult = false) { // --- Abort in this case! From 5fd875c0ce6da2a2eb451f55915e127654825a1b Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 28 Jul 2008 15:44:57 +0200 Subject: [PATCH 037/142] Added Maximize/Normalize button into the menu. Using this button, you can show and hide and header in your session to maximize useability of phpLogCon. --- src/include/functions_common.php | 2 + src/include/functions_frontendhelpers.php | 56 ++++++++++++++++++++++- src/lang/de/main.php | 3 ++ src/lang/en/main.php | 2 + src/lang/pt_BR/main.php | 2 + src/templates/include_footer.html | 1 - src/templates/include_header.html | 4 +- src/templates/include_menu.html | 6 +++ src/themes/dark/main.css | 8 ++++ src/themes/default/main.css | 7 +++ src/userchange.php | 13 +++++- 11 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 77a9ee1..ea2bc41 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -440,6 +440,8 @@ function InitFrontEndVariables() $content['MENU_SOURCE_DISK'] = $content['BASEPATH'] . "images/icons/document_text.png"; $content['MENU_SOURCE_DB'] = $content['BASEPATH'] . "images/icons/data_table.png"; $content['MENU_SOURCE_PDO'] = $content['BASEPATH'] . "images/icons/data_gear.png"; + $content['MENU_MAXIMIZE'] = $content['BASEPATH'] . "images/icons/table_selection_all.png"; + $content['MENU_NORMAL'] = $content['BASEPATH'] . "images/icons/table_selection_block.png"; $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; diff --git a/src/include/functions_frontendhelpers.php b/src/include/functions_frontendhelpers.php index c34716c..df02f8b 100644 --- a/src/include/functions_frontendhelpers.php +++ b/src/include/functions_frontendhelpers.php @@ -41,12 +41,28 @@ if ( !defined('IN_PHPLOGCON') ) function InitFrontEndDefaults() { + global $content; + // To create the current URL CreateCurrentUrl(); // --- BEGIN Main Info Area - + $content['MAXURL'] = $content['BASEPATH'] . "userchange.php?"; + if ( isset($_SESSION['SESSION_MAXIMIZED']) && $_SESSION['SESSION_MAXIMIZED'] == true ) + { + $content['MAXIMIZED'] = true; + $content['MAXIMAGE'] = $content['MENU_NORMAL']; + $content['MAXLANGTEXT'] = $content['LN_MENU_NORMALVIEW']; + $content['MAXURL'] .= "op=maximize&max=0"; + } + else + { + $content['MAXIMIZED'] = false; + $content['MAXIMAGE'] = $content['MENU_MAXIMIZE']; + $content['MAXLANGTEXT'] = $content['LN_MENU_MAXVIEW']; + $content['MAXURL'] .= "op=maximize&max=1"; + } // --- END Main Info Area @@ -65,6 +81,40 @@ function InstallFileReminder() } } +function GetAdditionalUrl($skipParam, $appendParam = "") +{ + global $content; +//echo $content['additional_url_full']; + if ( isset($content['additional_url_full']) && strlen($content['additional_url_full']) > 0 ) + { + if ( strlen($skipParam) > 0 ) + { + // remove parameters from string! + $szReturn = preg_replace("#(&{$skipParam}=[\w]+)#is", '', $content['additional_url_full']); + if ( strlen($szReturn) > 0 ) + { + if ( strlen($appendParam) > 0 ) + return $szReturn . "&" . $appendParam; + else + return $szReturn; + } + else if ( strlen($appendParam) > 0 ) + return "?" . $appendParam; + else + return ""; + } + else + return $content['additional_url_full']; + } + else + { + if ( strlen($appendParam) > 0 ) + return "?" . $appendParam; + else + return ""; + } +} + function CreateCurrentUrl() { global $content, $CFG; @@ -72,6 +122,7 @@ function CreateCurrentUrl() // Init additional_url helper variable $content['additional_url'] = ""; + $content['additional_url_full'] = ""; $content['additional_url_uidonly'] = ""; $content['additional_url_sortingonly'] = ""; $content['additional_url_sourceonly'] = ""; @@ -143,6 +194,9 @@ function CreateCurrentUrl() } else $content['additional_url'] .= "&" . $tmpvars[0] . "=" . $tmpvars[1]; + + // always append to this URL! + $content['additional_url_full'] .= "&" . $tmpvars[0] . "=" . $tmpvars[1]; } $hvCounter++; diff --git a/src/lang/de/main.php b/src/lang/de/main.php index e126ffd..1844500 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -69,6 +69,9 @@ $content['LN_MENU_LOGIN'] = "Login"; $content['LN_MENU_ADMINCENTER'] = "Admin Center"; $content['LN_MENU_LOGOFF'] = "Logoff"; $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; + $content['LN_MENU_MAXVIEW'] = "Maximize View"; + $content['LN_MENU_NORMALVIEW'] = "Normalize View"; + // Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Warnung! Du hast das Installationsscript 'install.php' noch nicht aus dem phpLogCon Hauptordner entfernt!"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 8fc40c2..a330118 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -70,6 +70,8 @@ $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_MENU_ADMINCENTER'] = "Admin Center"; $content['LN_MENU_LOGOFF'] = "Logoff"; $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; + $content['LN_MENU_MAXVIEW'] = "Maximize View"; + $content['LN_MENU_NORMALVIEW'] = "Normalize View"; // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Warning! You still have NOT removed the 'install.php' from your phpLogCon main directory!"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index abb861e..f4e46e9 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -73,6 +73,8 @@ $content['LN_GEN_SELECTVIEW'] = "Visão"; $content['LN_MENU_ADMINCENTER'] = "Admin Center"; $content['LN_MENU_LOGOFF'] = "Logoff"; $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; + $content['LN_MENU_MAXVIEW'] = "Maximize View"; + $content['LN_MENU_NORMALVIEW'] = "Normalize View"; // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Atenção! Você ainda NÃO removeu o arquivo 'install.php' do diretório de seu phpLogCon!"; diff --git a/src/templates/include_footer.html b/src/templates/include_footer.html index 7da75ef..8e46353 100644 --- a/src/templates/include_footer.html +++ b/src/templates/include_footer.html @@ -1,4 +1,3 @@ -

{LN_VIEWS_ADDEDIT}
diff --git a/src/templates/include_header.html b/src/templates/include_header.html index c89a45b..28634ee 100644 --- a/src/templates/include_header.html +++ b/src/templates/include_header.html @@ -11,6 +11,7 @@ +
@@ -65,7 +66,6 @@ -
@@ -116,7 +116,7 @@
- + diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index bb65c31..25cc2dd 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -23,5 +23,11 @@ + + + +
  + {MAXLANGTEXT} +
diff --git a/src/themes/dark/main.css b/src/themes/dark/main.css index 135399f..b9ec9f2 100644 --- a/src/themes/dark/main.css +++ b/src/themes/dark/main.css @@ -236,6 +236,14 @@ font color: #FFFFFF; background-color: #290604; } +.topmenuextra +{ + height: 16px; + font: 10px Verdana, Arial, Helvetica, sans-serif; + color: #FFFFFF; + background-color: #053841; +} + .topmenu2begin { height: 20px; diff --git a/src/themes/default/main.css b/src/themes/default/main.css index 770eba7..082bd71 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -239,6 +239,13 @@ font color: #FFFFFF; background-color: #597196; } +.topmenuextra +{ + height: 20px; + font: 10px Verdana, Arial, Helvetica, sans-serif; + color: #FFFFFF; + background-color: #B8D4E0; +} .topmenu2begin { height: 20px; diff --git a/src/userchange.php b/src/userchange.php index 0b84233..7ded33c 100644 --- a/src/userchange.php +++ b/src/userchange.php @@ -80,8 +80,17 @@ if ( isset($_GET['op']) ) exit; } } - - + if ( $_GET['op'] == "maximize" && isset($_GET['max']) ) + { + if ( intval($_GET['max']) == 1 ) + { + $_SESSION['SESSION_MAXIMIZED'] = true; + } + else + { + $_SESSION['SESSION_MAXIMIZED'] = false; + } + } if ( $_GET['op'] == "changepagesize" && isset($_GET['pagesizeid']) ) { if ( intval($_GET['pagesizeid']) >= 0 && intval($_GET['pagesizeid']) < count($content['pagesizes']) ) From 354c17411810aa5677a8a3bfca4e58c56561543a Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 28 Jul 2008 17:23:26 +0200 Subject: [PATCH 038/142] If UserDB is enabled, the donate button will be hidden on normal phpLogCon pages. It will only be visible in the Admin Center. --- src/include/functions_common.php | 1 + src/include/functions_users.php | 7 ++++++- src/templates/include_header.html | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index ea2bc41..bfb9c65 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -65,6 +65,7 @@ $LANG = "en"; // Default language $content['BUILDNUMBER'] = "2.5.0"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; +$content['SHOW_DONATEBUTTON'] = true; // Default = true! $content['EXTRA_METATAGS'] = ""; $content['EXTRA_JAVASCRIPT'] = ""; $content['EXTRA_STYLESHEET'] = ""; diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 081110e..79a2933 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -54,6 +54,11 @@ function InitUserSession() { global $content; + // --- Hide donate Button if not on Admin Page + if ( !defined('IS_ADMINPAGE') ) + $content['SHOW_DONATEBUTTON'] = false; + // --- + if ( isset($_SESSION['SESSION_LOGGEDIN']) ) { if ( !$_SESSION['SESSION_LOGGEDIN'] ) @@ -72,7 +77,7 @@ function InitUserSession() $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; if ( isset($_SESSION['SESSION_GROUPIDS']) ) $content['SESSION_GROUPIDS'] = $_SESSION['SESSION_GROUPIDS']; - + // Successfully logged in return true; } diff --git a/src/templates/include_header.html b/src/templates/include_header.html index 28634ee..ca1cab5 100644 --- a/src/templates/include_header.html +++ b/src/templates/include_header.html @@ -15,10 +15,12 @@ - - + + + + From 6e47202f00f6263b5d50d22d5f68045c0df88cf5 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 29 Jul 2008 14:27:58 +0200 Subject: [PATCH 040/142] Added changelog entry --- ChangeLog | 16 ++++++++++++++++ src/images/icons/table_selection_all.png | Bin 0 -> 297 bytes src/images/icons/table_selection_block.png | Bin 0 -> 507 bytes src/include/functions_common.php | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/images/icons/table_selection_all.png create mode 100644 src/images/icons/table_selection_block.png diff --git a/ChangeLog b/ChangeLog index 6033891..585b3d7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,20 @@ --------------------------------------------------------------------------- +Version 2.5.1 (beta), 2008-07-29 +- Added a new option to suppress displaying multiple messages. This + means if you have two or more messages of the exact same text one after + another, only ONE message will be shown. This helps to "compress" the + logview a little bit. +- New feature, added Maximize/Normalize button into the menu. You can + hide the header now by using this feature for maximum log display. +- If the UserDB System is enabled, the donate button will only being + shown within the Admin Center. +- Fixed a bug in the input checking of files when a Diskfile source + was added or reconfigured. +- Fixed minor bug reading the correct default view for sources from DB. +- Fixed a problem in the Views Admin, a set of predefined columns could be + in the list. +- Enhanced error display in all admin templates. +--------------------------------------------------------------------------- Version 2.3.8 (beta), 2008-07-28 - Fixed a "notice" bug in the installer, which was missing to save the DBType for MYSQL Datasource. diff --git a/src/images/icons/table_selection_all.png b/src/images/icons/table_selection_all.png new file mode 100644 index 0000000000000000000000000000000000000000..22764f504458a2bc5916168650fdcec1d529939a GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`S|zR#CC){ui6xo&c?uz!xv2~; znF=NfzC~3EMg|7P3I+yNMkZDUCJNdHh6VNn{1`n><|{Ln>}1CoB*!XgWFb|J}wX zd_rHV43-$bOKB`BJ7c02_k2=24^R932Hlht$7vq-f3{f&S5!zySXdnJ5lab{baC;Ly&44SO30MOM$Lo@O1TaS?83{1OOi=Vq5?K literal 0 HcmV?d00001 diff --git a/src/images/icons/table_selection_block.png b/src/images/icons/table_selection_block.png new file mode 100644 index 0000000000000000000000000000000000000000..7b7f3424ed5a3f8199fde4a4421d1b40e23a579e GIT binary patch literal 507 zcmVWdKBJAT%INa(W;#FfcP9FfckYG&(ReAS*C2FfiRo4vqi-00(qQO+^RO z0|OTx9MnR`dH?_b32;bRa{vGe@Bjb`@Bu=sG?)MY00d`2O+f$vv5yP5H0gB_6U z0w?h!AvBoKATZwSak8AoAWSiDXYbydx0799!?1Mr=_t(Qe{zrESrvO;l>LL%nb*(2 z!$3E*Z1aOx8LXy|$pGoJUfQNAve`8!;S)(Ii6h2VOVVgWRIf|w_lasXNkKqVtx9S% zh?ukFfI}$-adiQF46*z60yr-A=eQH%{rRo_uh&yy7J8jB1%wcqjFZD?BZ0+YL@E@- zHz$>JfURxd<}TVuU=S$7K|)|Uop<=d0e7Cw0N4G+!FUWbn}&nQ=MU_56?k;9lcga} zCP1kq@N{;mowVBu`Fw=jvC|3B5n6=z@fz{st4xo33)T2Y+>M3VHrKq4>(lp8#IvlF x>3}=M$b?*Oab~U=xpthz3^smD>)l) Date: Tue, 29 Jul 2008 17:30:47 +0200 Subject: [PATCH 041/142] All $cfg variables are now accessed through a propper helper function. This preparation is needed to implement that the user can overwrite the global general settings later, when userdb system is enabled. --- src/classes/class_template.php | 3 +- src/classes/logstreamdb.class.php | 4 +- src/classes/logstreampdo.class.php | 3 +- src/convert.php | 19 +++--- src/include/constants_general.php | 6 ++ src/include/functions_common.php | 72 +++++++++++++++-------- src/include/functions_config.php | 33 ++++++----- src/include/functions_db.php | 53 +++++++---------- src/include/functions_filters.php | 4 +- src/include/functions_frontendhelpers.php | 15 +++-- src/include/functions_users.php | 4 +- src/index.php | 9 ++- 12 files changed, 122 insertions(+), 103 deletions(-) diff --git a/src/classes/class_template.php b/src/classes/class_template.php index 9cce45d..e7b4bcf 100644 --- a/src/classes/class_template.php +++ b/src/classes/class_template.php @@ -98,9 +98,8 @@ class Template { function parser ($vars = '', $filename = '') { // BEGIN DELTA MOD - global $CFG; // For MiscShowPageRenderStats - if ( $CFG['MiscShowPageRenderStats'] == 1 ) + if ( GetConfigSetting("MiscShowPageRenderStats", 1, CFGLEVEL_USER) == 1 ) FinishPageRenderStats( $vars ); // END DELTA MOD diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index afd2015..af1375c 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -772,9 +772,7 @@ class LogStreamDB extends LogStream { */ private function PrintDebugError($szErrorMsg) { - global $CFG; - - if ( isset($CFG['MiscShowDebugMsg']) && $CFG['MiscShowDebugMsg'] == 1 ) + if ( GetConfigSetting("MiscShowDebugMsg", 0, CFGLEVEL_USER) == 1 ) { $errdesc = mysql_error(); $errno = mysql_errno(); diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index ef8eeb4..5354cb6 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -788,8 +788,7 @@ class LogStreamPDO extends LogStream { */ private function PrintDebugError($szErrorMsg) { - global $CFG; - if ( isset($CFG['MiscShowDebugMsg']) && $CFG['MiscShowDebugMsg'] == 1 ) + if ( GetConfigSetting("MiscShowDebugMsg", 0, CFGLEVEL_USER) == 1 ) { $errdesc = $this->_dbhandle == null ? "" : implode( ";", $this->_dbhandle->errorInfo() ); $errno = $this->_dbhandle == null ? "" : $this->_dbhandle->errorCode(); diff --git a/src/convert.php b/src/convert.php index c3631e8..09d46a2 100644 --- a/src/convert.php +++ b/src/convert.php @@ -52,8 +52,9 @@ InitFrontEndDefaults(); // Only in WebFrontEnd // --- PreCheck if conversion is allowed! if ( - (isset($CFG['UserDBEnabled']) && $CFG['UserDBEnabled']) && - (isset($CFG['UserDBConvertAllowed']) && $CFG['UserDBConvertAllowed']) + + GetConfigSetting("UserDBEnabled", false) && + GetConfigSetting("UserDBConvertAllowed", false) ) { // Setup static values @@ -113,16 +114,14 @@ $content['LN_CONVERT_TITLETOP'] = GetAndReplaceLangStr( $content['LN_CONVERT_TIT if ( $content['CONVERT_STEP'] == 2 ) { // Check the database connect - $link_id = mysql_connect( $CFG['UserDBServer'], $CFG['UserDBUser'], $CFG['UserDBPass']); + $link_id = mysql_connect( GetConfigSetting("UserDBServer"), GetConfigSetting("UserDBUser"), GetConfigSetting("UserDBPass") ); if (!$link_id) - RevertOneStep( $content['CONVERT_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORCONNECTFAILED'], $CFG['UserDBServer']) . "
" . DB_ReturnSimpleErrorMsg() ); + RevertOneStep( $content['CONVERT_STEP']-1, GetAndReplaceLangStr( $content['LN_INSTALL_ERRORCONNECTFAILED'], GetConfigSetting("UserDBServer") . "
" . DB_ReturnSimpleErrorMsg() ) ); // Try to select the DB! - $db_selected = mysql_select_db($CFG['UserDBName'], $link_id); + $db_selected = mysql_select_db(GetConfigSetting("UserDBName"), $link_id); if(!$db_selected) - RevertOneStep( $content['CONVERT_STEP']-1,GetAndReplaceLangStr( $content['LN_INSTALL_ERRORACCESSDENIED'], $CFG['UserDBName']) . "
" . DB_ReturnSimpleErrorMsg()); - - + RevertOneStep( $content['CONVERT_STEP']-1,GetAndReplaceLangStr( $content['LN_INSTALL_ERRORACCESSDENIED'], GetConfigSetting("UserDBName") . "
" . DB_ReturnSimpleErrorMsg() ) ); } else if ( $content['CONVERT_STEP'] == 3 ) { @@ -145,7 +144,7 @@ else if ( $content['CONVERT_STEP'] == 3 ) } // Replace stats_ with the custom one ;) - $totaldbdefs = str_replace( "`logcon_", "`" . $CFG["UserDBPref"], $totaldbdefs ); + $totaldbdefs = str_replace( "`logcon_", "`" . GetConfigSetting("UserDBPref"), $totaldbdefs ); // Now split by sql command $mycommands = split( ";\n", $totaldbdefs ); @@ -163,7 +162,7 @@ else if ( $content['CONVERT_STEP'] == 3 ) } // Append INSERT Statement for Config Table to set the Database Version ^^! - $mycommands[count($mycommands)] = "INSERT INTO `" . $CFG["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', 1)"; + $mycommands[count($mycommands)] = "INSERT INTO `" . GetConfigSetting("UserDBPref") . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', 1)"; // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! diff --git a/src/include/constants_general.php b/src/include/constants_general.php index f0c737f..aa71f89 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -57,6 +57,12 @@ define('STR_DEBUG_WARN', "Warning"); define('STR_DEBUG_ERROR', "Error"); define('STR_DEBUG_ERROR_WTF', "WTF OMFG"); +// --- Config Level defines +define('CFGLEVEL_GLOBAL', 0); +define('CFGLEVEL_GROUP', 1); +define('CFGLEVEL_USER', 2); +// --- + // --- Source Type defines define('SOURCE_DISK', '1'); define('SOURCE_DB', '2'); diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 6e53166..bb0a209 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -81,7 +81,7 @@ if ( $myPhpVerArray[0] < 5 ) function InitBasicPhpLogCon() { // Needed to make global - global $CFG, $gl_root_path, $content; + global $gl_root_path, $content; // Check RunMode first! CheckAndSetRunMode(); @@ -99,9 +99,9 @@ function InitBasicPhpLogCon() function InitUserSystemPhpLogCon() { // global vars needed - global $CFG, $gl_root_path, $content; + global $gl_root_path, $content; - if ( isset($CFG['UserDBEnabled']) && $CFG['UserDBEnabled'] ) + if ( GetConfigSetting("UserDBEnabled", false) ) { // Include User Functions include($gl_root_path . 'include/functions_users.php'); @@ -135,7 +135,7 @@ function GetFileLength($szFileName) function InitPhpLogCon() { // Needed to make global - global $CFG, $gl_root_path, $content; + global $gl_root_path, $content; // Init Basics which do not need a database InitBasicPhpLogCon(); @@ -150,7 +150,7 @@ function InitPhpLogCon() InitRuntimeInformations(); // Establish DB Connection - if ( $CFG['UserDBEnabled'] ) + if ( GetConfigSetting("UserDBEnabled", false) ) DB_Connect(); // Now load the Page configuration values @@ -272,10 +272,11 @@ function CreateDBTypesList( $selectedDBType ) function CreatePagesizesList() { - global $CFG, $content; + global $content; + $tmpViewsPerPage = GetConfigSetting("ViewEntriesPerPage", 50, CFGLEVEL_USER); $iCounter = 0; - $content['pagesizes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => $content['LN_GEN_PRECONFIGURED'] . " (" . $CFG['ViewEntriesPerPage'] . ")", "Value" => $CFG['ViewEntriesPerPage'] ); $iCounter++; + $content['pagesizes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => $content['LN_GEN_PRECONFIGURED'] . " (" . $tmpViewsPerPage . ")", "Value" => $tmpViewsPerPage ); $iCounter++; $content['pagesizes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => " 25 " . $content['LN_GEN_RECORDSPERPAGE'], "Value" => 25 ); $iCounter++; $content['pagesizes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => " 50 " . $content['LN_GEN_RECORDSPERPAGE'], "Value" => 50 ); $iCounter++; $content['pagesizes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => " 75 " . $content['LN_GEN_RECORDSPERPAGE'], "Value" => 75 ); $iCounter++; @@ -292,14 +293,15 @@ function CreatePagesizesList() function CreateReloadTimesList() { - global $CFG, $content; + global $content; -// $CFG['ViewEnableAutoReloadSeconds'] $iCounter = 0; $content['reloadtimes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => $content['LN_AUTORELOAD_DISABLED'], "Value" => 0 ); $iCounter++; - if ( isset($CFG['ViewEnableAutoReloadSeconds']) && $CFG['ViewEnableAutoReloadSeconds'] > 0 ) + + $tmpReloadSeconds = GetConfigSetting("ViewEnableAutoReloadSeconds", "", CFGLEVEL_USER); + if ( $tmpReloadSeconds > 0 ) { - $content['reloadtimes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => $content['LN_AUTORELOAD_PRECONFIGURED'] . " (" . $CFG['ViewEnableAutoReloadSeconds'] . " " . $content['LN_AUTORELOAD_SECONDS'] . ") ", "Value" => $CFG['ViewEnableAutoReloadSeconds'] ); $iCounter++; + $content['reloadtimes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => $content['LN_AUTORELOAD_PRECONFIGURED'] . " (" . $tmpReloadSeconds . " " . $content['LN_AUTORELOAD_SECONDS'] . ") ", "Value" => $tmpReloadSeconds ); $iCounter++; } $content['reloadtimes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => " 5 " . $content['LN_AUTORELOAD_SECONDS'], "Value" => 5 ); $iCounter++; $content['reloadtimes'][$iCounter] = array( "ID" => $iCounter, "Selected" => "", "DisplayName" => " 10 " . $content['LN_AUTORELOAD_SECONDS'], "Value" => 10 ); $iCounter++; @@ -349,10 +351,10 @@ function CreatePredefinedSearches() function InitPhpDebugMode() { - global $content, $CFG; + global $content; // --- Set Global DEBUG Level! - if ( $CFG['MiscShowDebugMsg'] == 1 ) + if ( GetConfigSetting("MiscShowDebugMsg", 0, CFGLEVEL_USER) == 1 ) ini_set( "error_reporting", E_ALL ); // ALL PHP MESSAGES! else ini_set( "error_reporting", E_ERROR ); // ONLY PHP ERROR'S! @@ -372,12 +374,12 @@ function CheckAndSetRunMode() function InitRuntimeInformations() { - global $content, $CFG; + global $content; // TODO| maybe not needed! // Enable GZIP Compression if enabled! - if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false && (isset($CFG['MiscEnableGzipCompression']) && $CFG['MiscEnableGzipCompression'] == 1) ) + if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false && GetConfigSetting("MiscEnableGzipCompression", 1, CFGLEVEL_USER) == 1 ) { // This starts gzip compression! ob_start("ob_gzhandler"); @@ -491,7 +493,7 @@ function InitConfigurationValues() if ( !defined('IN_PHPLOGCON_CONVERT') ) { // If Database is enabled, try to read from database! - if ( $CFG['UserDBEnabled'] ) + if ( GetConfigSetting("UserDBEnabled", false) ) { // Get configuration variables $result = DB_Query("SELECT * FROM " . DB_CONFIG . " WHERE is_global = true"); @@ -519,7 +521,7 @@ function InitConfigurationValues() { // Check if user needs to be logged in - if ( isset($CFG["UserDBLoginRequired"]) && $CFG["UserDBLoginRequired"] == true ) + if ( GetConfigSetting("UserDBLoginRequired", false) ) { // User needs to be logged in, redirect to login page if ( !defined("IS_NOLOGINPAGE") ) @@ -569,7 +571,7 @@ function InitConfigurationValues() } else // Failsave! { - $content['user_lang'] = $CFG['ViewDefaultLanguage'] /*"en"*/; + $content['user_lang'] = GetConfigSetting("ViewDefaultLanguage", "en", CFGLEVEL_USER) /*"en"*/; $LANG = $content['user_lang']; $content['gen_lang'] = $content['user_lang']; } @@ -585,14 +587,14 @@ function InitConfigurationValues() // Auto reload handling! if ( !isset($_SESSION['AUTORELOAD_ID']) ) { - if ( isset($CFG['ViewEnableAutoReloadSeconds']) && $CFG['ViewEnableAutoReloadSeconds'] > 0 ) + if ( GetConfigSetting("ViewEnableAutoReloadSeconds", 0, CFGLEVEL_USER) > 0 ) $_SESSION['AUTORELOAD_ID'] = 1; // Autoreload ID will be the first item! else // Default is 0, which means auto reload disabled $_SESSION['AUTORELOAD_ID'] = 0; } // Theme Handling - if ( !isset($content['web_theme']) ) { $content['web_theme'] = $CFG['ViewDefaultTheme'] /*"default"*/; } + if ( !isset($content['web_theme']) ) { $content['web_theme'] = GetConfigSetting("ViewDefaultTheme", "default", CFGLEVEL_USER); } if ( isset($_SESSION['CUSTOM_THEME']) && VerifyTheme($_SESSION['CUSTOM_THEME']) ) $content['user_theme'] = $_SESSION['CUSTOM_THEME']; else @@ -700,10 +702,11 @@ function DieWithFriendlyErrorMsg( $szerrmsg ) */ function InitPageTitle() { - global $content, $CFG, $currentSourceID; + global $content, $currentSourceID; - if ( isset($CFG['PrependTitle']) && strlen($CFG['PrependTitle']) > 0 ) - $szReturn = $CFG['PrependTitle'] . " :: "; + $tmpTitle = GetConfigSetting("PrependTitle", ""); + if ( strlen($tmpTitle) > 0 ) + $szReturn = $tmpTitle . " :: "; else $szReturn = ""; @@ -885,10 +888,10 @@ function GetMonthFromString($szMonth) */ function AddContextLinks(&$sourceTxt) { - global $szTLDDomains, $CFG; + global $szTLDDomains; // Return if not enabled! - if ( !isset($CFG['EnableIPAddressResolve']) || $CFG['EnableIPAddressResolve'] == 1 ) + if ( GetConfigSetting("EnableIPAddressResolve", 0, CFGLEVEL_USER) == 1 ) { // Search for IP's and Add Reverse Lookup first! $sourceTxt = preg_replace( '/([^\[])\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/e', "'\\1\\2.\\3.\\4.\\5' . ReverseResolveIP('\\2.\\3.\\4.\\5', ' {', '} ')", $sourceTxt ); @@ -1076,4 +1079,23 @@ function SaveGeneralSettingsIntoDB() WriteConfigValue( "DefaultSourceID", true ); } +function GetConfigSetting($szSettingName, $szDefaultValue = "", $DesiredConfigLevel = CFGLEVEL_GLOBAL) +{ + global $content, $CFG; + + if ( isset($CFG['UserDBEnabled']) && $CFG['UserDBEnabled'] ) + { + if ( $DesiredConfigLevel == CFGLEVEL_USER ) + { + // TODO! + } + } + + // Either UserDB disabled, or global setting wanted - easier handling + if ( isset($CFG[$szSettingName]) ) + return $CFG[$szSettingName]; + else + return $szDefaultValue; +} + ?> \ No newline at end of file diff --git a/src/include/functions_config.php b/src/include/functions_config.php index a77179f..d884c12 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -76,7 +76,8 @@ function InitSourceConfigs() // --- // Set default view id to source - $szDefaultViewID = isset($CFG['DefaultViewsID']) && strlen($CFG['DefaultViewsID']) > 0 ? $CFG['DefaultViewsID'] : "SYSLOG"; + $tmpVar = GetConfigSetting("DefaultViewsID", "", CFGLEVEL_USER); + $szDefaultViewID = strlen($tmpVar) > 0 ? $tmpVar : "SYSLOG"; if ( isset($_SESSION[$iSourceID . "-View"]) ) { @@ -169,7 +170,7 @@ function InitSourceConfigs() } // Set generic configuration options - $content['Sources'][$iSourceID]['ObjRef']->_pageCount = $CFG['ViewEntriesPerPage']; + $content['Sources'][$iSourceID]['ObjRef']->_pageCount = GetConfigSetting("ViewEntriesPerPage", 50); // Set default SourceID here! if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) @@ -191,9 +192,10 @@ function InitSourceConfigs() $currentSourceID = $_SESSION['currentSourceID']; else { - if ( isset($CFG['DefaultSourceID']) && isset($content['Sources'][ $CFG['DefaultSourceID'] ]) ) + $tmpVar = GetConfigSetting("DefaultSourceID", "", CFGLEVEL_USER); + if ( isset($content['Sources'][ $tmpVar ]) ) // Set Source to preconfigured sourceID! - $_SESSION['currentSourceID'] = $CFG['DefaultSourceID']; + $_SESSION['currentSourceID'] = $tmpVar; else // No Source stored in session, then to so now! $_SESSION['currentSourceID'] = $currentSourceID; @@ -211,7 +213,8 @@ function InitSourceConfigs() $content['Views'][ $currentViewID ]['selected'] = "selected"; // If DEBUG Mode is enabled, we prepend the UID field into the col list! - if ( $CFG['MiscShowDebugMsg'] == 1 && isset($content['Views'][$currentViewID]) ) + + if ( GetConfigSetting("MiscShowDebugMsg", 0, CFGLEVEL_USER) == 1 && isset($content['Views'][$currentViewID]) ) array_unshift( $content['Views'][$currentViewID]['Columns'], SYSLOG_UID); // --- } @@ -262,7 +265,8 @@ function AppendLegacyColumns() ); // set default to legacy of no default view is specified! - if ( !isset($CFG['DefaultViewsID']) || strlen($CFG['DefaultViewsID']) <= 0 ) + $tmpVar = GetConfigSetting("DefaultViewsID", "", CFGLEVEL_USER); + if ( strlen($tmpVar) <= 0 ) $CFG['DefaultViewsID'] = "LEGACY"; } @@ -277,13 +281,14 @@ function InitPhpLogConConfigFile($bHandleMissing = true) include_once($gl_root_path . 'config.php'); // Easier DB Access - define('DB_CONFIG', $CFG['UserDBPref'] . "config"); - define('DB_GROUPS', $CFG['UserDBPref'] . "groups"); - define('DB_GROUPMEMBERS', $CFG['UserDBPref'] . "groupmembers"); - define('DB_SEARCHES', $CFG['UserDBPref'] . "searches"); - define('DB_SOURCES', $CFG['UserDBPref'] . "sources"); - define('DB_USERS', $CFG['UserDBPref'] . "users"); - define('DB_VIEWS', $CFG['UserDBPref'] . "views"); + $tblPref = GetConfigSetting("UserDBPref", "logcon"); + define('DB_CONFIG', $tblPref . "config"); + define('DB_GROUPS', $tblPref . "groups"); + define('DB_GROUPMEMBERS', $tblPref . "groupmembers"); + define('DB_SEARCHES', $tblPref . "searches"); + define('DB_SOURCES', $tblPref . "sources"); + define('DB_USERS', $tblPref . "users"); + define('DB_VIEWS', $tblPref . "views"); // Legacy support for old columns definition format! if ( isset($CFG['Columns']) && is_array($CFG['Columns']) ) @@ -295,7 +300,7 @@ function InitPhpLogConConfigFile($bHandleMissing = true) // --- // For MiscShowPageRenderStats - if ( $CFG['MiscShowPageRenderStats'] == 1 ) + if ( GetConfigSetting("MiscShowPageRenderStats", 1) ) { $content['ShowPageRenderStats'] = "true"; InitPageRenderStats(); diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 7aad19f..5b4a383 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -51,16 +51,16 @@ $content['database_installedversion'] = "0"; // 0 is default which means Prior V function DB_Connect() { - global $userdbconn, $CFG; + global $userdbconn; // Avoid if already OPEN if ($userdbconn) return; //TODO: Check variables first - $userdbconn = mysql_connect($CFG['UserDBServer'],$CFG['UserDBUser'],$CFG['UserDBPass']); + $userdbconn = mysql_connect( GetConfigSetting("UserDBServer"), GetConfigSetting("UserDBUser"), GetConfigSetting("UserDBPass")); if (!$userdbconn) - DB_PrintError("Link-ID == false, connect to ".$CFG['UserDBServer']." failed", true); + DB_PrintError("Link-ID == false, connect to " . GetConfigSetting("UserDBServer") . " failed", true); // --- Now, check Mysql DB Version! $strmysqlver = mysql_get_server_info(); @@ -82,9 +82,9 @@ function DB_Connect() } // --- - $db_selected = mysql_select_db($CFG['UserDBName'], $userdbconn); + $db_selected = mysql_select_db( GetConfigSetting("UserDBName"), $userdbconn ); if(!$db_selected) - DB_PrintError("Cannot use database '" . $CFG['UserDBName'] . "'", true); + DB_PrintError("Cannot use database '" .GetConfigSetting("UserDBName") . "'", true); // :D Success connecting to db // TODO Do some more validating on the database @@ -99,8 +99,7 @@ function DB_Disconnect() function DB_Query($query_string, $bProcessError = true, $bCritical = false) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -118,8 +117,7 @@ function DB_Query($query_string, $bProcessError = true, $bCritical = false) function DB_FreeQuery($query_id) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -130,8 +128,7 @@ function DB_FreeQuery($query_id) function DB_GetRow($query_id) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -143,8 +140,7 @@ function DB_GetRow($query_id) function DB_GetSingleRow($query_id, $bClose) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -165,8 +161,7 @@ function DB_GetSingleRow($query_id, $bClose) function DB_GetAllRows($query_id, $bClose) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -191,8 +186,7 @@ function DB_GetAllRows($query_id, $bClose) function DB_GetMysqlStats() { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -209,7 +203,7 @@ function DB_ReturnSimpleErrorMsg() function DB_PrintError($MyErrorMsg, $DieOrNot) { - global $n,$HTTP_COOKIE_VARS, $errdesc, $errno, $linesep, $CFG; + global $n,$HTTP_COOKIE_VARS, $errdesc, $errno, $linesep; $errdesc = mysql_error(); $errno = mysql_errno(); @@ -260,8 +254,7 @@ function DB_StripSlahes($myString) function DB_ReturnLastInsertID($myResult = false) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -272,8 +265,7 @@ function DB_ReturnLastInsertID($myResult = false) function DB_GetRowCount($query) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -288,8 +280,7 @@ function DB_GetRowCount($query) function DB_GetRowCountByResult($myresult) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -300,8 +291,7 @@ function DB_GetRowCountByResult($myresult) function DB_Exec($query) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -323,9 +313,10 @@ function PrepareValueForDB($szValue) function WriteConfigValue($szPropName, $is_global = true, $userid = false, $groupid = false) { + global $content; + // --- Abort in this case! - global $CFG, $content; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -366,8 +357,7 @@ function WriteConfigValue($szPropName, $is_global = true, $userid = false, $grou function GetSingleDBEntryOnly( $myqry ) { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- @@ -384,8 +374,7 @@ function GetSingleDBEntryOnly( $myqry ) function GetRowsAffected() { // --- Abort in this case! - global $CFG; - if ( $CFG['UserDBEnabled'] == false ) + if ( GetConfigSetting("UserDBEnabled", false) == false ) return; // --- diff --git a/src/include/functions_filters.php b/src/include/functions_filters.php index 66ad2e6..810b2dd 100644 --- a/src/include/functions_filters.php +++ b/src/include/functions_filters.php @@ -41,7 +41,7 @@ require_once($gl_root_path . 'include/constants_filters.php'); function InitFilterHelpers() { - global $CFG, $content, $filters; + global $content, $filters; // Init Default DateMode from SESSION! if ( isset($_SESSION['filter_datemode']) ) @@ -196,7 +196,7 @@ function InitFilterHelpers() function FillDateRangeArray($sourcearray, $szArrayListName, $szFilterName) // $content['years'], "filter_daterange_from_year_list", "filter_daterange_from_year") { - global $CFG, $content, $filters; + global $content, $filters; $iCount = count($sourcearray); for ( $i = 0; $i < $iCount; $i++) diff --git a/src/include/functions_frontendhelpers.php b/src/include/functions_frontendhelpers.php index df02f8b..e357369 100644 --- a/src/include/functions_frontendhelpers.php +++ b/src/include/functions_frontendhelpers.php @@ -117,7 +117,7 @@ function GetAdditionalUrl($skipParam, $appendParam = "") function CreateCurrentUrl() { - global $content, $CFG; + global $content; $content['CURRENTURL'] = $_SERVER['PHP_SELF']; // . "?" . $_SERVER['QUERY_STRING'] // Init additional_url helper variable @@ -131,11 +131,12 @@ function CreateCurrentUrl() $hvCounter = 0; // Append SourceID into everything! - if ( (isset($CFG['DefaultSourceID']) && isset($content['Sources'][ $CFG['DefaultSourceID'] ])) && isset($_SESSION['currentSourceID']) ) + $tmpDefSourceID = GetConfigSetting("DefaultSourceID", "", CFGLEVEL_USER); + if ( isset($content['Sources'][ $tmpDefSourceID ]) && isset($_SESSION['currentSourceID']) ) { // If the DefaultSourceID differes from the SourceID in our Session, we will append the sourceid within all URL's! - if ( $CFG['DefaultSourceID'] != $_SESSION['currentSourceID'] ) + if ( $tmpDefSourceID != $_SESSION['currentSourceID'] ) { // $content['additional_url'] .= "&sourceid=" . $_SESSION['currentSourceID']; $content['additional_url_uidonly'] = "&sourceid=" . $_SESSION['currentSourceID']; @@ -210,13 +211,13 @@ function CreateCurrentUrl() function GetFormatedDate($evttimearray) { - global $content, $CFG; + global $content; if ( !is_array($evttimearray) ) return $evttimearray; if ( - ( isset($CFG['ViewUseTodayYesterday']) && $CFG['ViewUseTodayYesterday'] == 1 ) + GetConfigSetting("ViewUseTodayYesterday", 0, CFGLEVEL_USER) == 1 && ( date('m', $evttimearray[EVTIME_TIMESTAMP]) == date('m') && date('Y', $evttimearray[EVTIME_TIMESTAMP]) == date('Y') ) ) @@ -233,9 +234,7 @@ function GetFormatedDate($evttimearray) function OutputDebugMessage($szDbg) { - global $CFG; - - if ( $CFG['MiscShowDebugMsg'] == 1 ) + if ( GetConfigSetting("MiscShowDebugMsg", 0, CFGLEVEL_USER) == 1 ) { print("
+ + Satisfied with phpLogCon?
Donate and help keep the project alive! +
From 4aefc080cf504739238405ca6ddcc143d5541bf5 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 29 Jul 2008 14:10:18 +0200 Subject: [PATCH 039/142] Added new feature to suppress display of multiple messages --- src/admin/index.php | 2 ++ src/include/config.sample.php | 1 + src/include/functions_common.php | 1 + src/index.php | 25 +++++++++++++++++++++++++ src/lang/de/admin.php | 1 + src/lang/en/admin.php | 2 +- src/lang/pt_BR/admin.php | 1 + src/templates/admin/admin_index.html | 5 ++++- 8 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index a4df05b..53cff38 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -93,6 +93,7 @@ if ( isset($_POST['op']) && $content['EditAllowed'] ) if ( isset ($_POST['MiscShowPageRenderStats']) ) { $content['MiscShowPageRenderStats'] = 1; } else { $content['MiscShowPageRenderStats'] = 0; } if ( isset ($_POST['MiscEnableGzipCompression']) ) { $content['MiscEnableGzipCompression'] = 1; } else { $content['MiscEnableGzipCompression'] = 0; } if ( isset ($_POST['DebugUserLogin']) ) { $content['DebugUserLogin'] = 1; } else { $content['DebugUserLogin'] = 0; } + if ( isset ($_POST['SuppressDuplicatedMessages']) ) { $content['SuppressDuplicatedMessages'] = 1; } else { $content['SuppressDuplicatedMessages'] = 0; } // Read Text number fields if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_POST['ViewMessageCharacterLimit']; } @@ -123,6 +124,7 @@ if ($content['MiscShowDebugGridCounter'] == 1) { $content['MiscShowDebugGridCoun if ($content['MiscShowPageRenderStats'] == 1) { $content['MiscShowPageRenderStats_checked'] = "checked"; } else { $content['MiscShowPageRenderStats_checked'] = ""; } if ($content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompression_checked'] = "checked"; } else { $content['MiscEnableGzipCompression_checked'] = ""; } if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } +if ($content['SuppressDuplicatedMessages'] == 1) { $content['SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['SuppressDuplicatedMessages_checked'] = ""; } // --- // --- Init for DefaultView field! diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 3dcc336..4de4764 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -76,6 +76,7 @@ $CFG['SearchCustomButtonCaption'] = "I'd like to feel sad"; // Default caption f $CFG['SearchCustomButtonSearch'] = "error"; // Default search string for the custom search button $CFG['EnableIPAddressResolve'] = 1; // If enabled, IP Addresses inline messages are automatically resolved and the result is added in brackets {} behind the IP Address +$CFG['SuppressDuplicatedMessages'] = 0; // If enabled, duplicated messages will be suppressed in the main display. // --- // --- Define which fields you want to see diff --git a/src/include/functions_common.php b/src/include/functions_common.php index bfb9c65..2da004c 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1061,6 +1061,7 @@ function SaveGeneralSettingsIntoDB() WriteConfigValue( "MiscShowPageRenderStats", true ); WriteConfigValue( "MiscEnableGzipCompression", true ); WriteConfigValue( "DebugUserLogin", true ); + WriteConfigValue( "SuppressDuplicatedMessages", true ); WriteConfigValue( "ViewMessageCharacterLimit", true ); WriteConfigValue( "ViewEntriesPerPage", true ); diff --git a/src/index.php b/src/index.php index fb3b1c4..7e86e68 100644 --- a/src/index.php +++ b/src/index.php @@ -298,6 +298,31 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c //Loop through the messages! do { + // --- Extra stuff for suppressing messages + if ( + isset($CFG['SuppressDuplicatedMessages']) && $CFG['SuppressDuplicatedMessages'] == 1 + && + isset($logArray[SYSLOG_MESSAGE]) + ) + { + + if ( !isset($szLastMessage) ) // Only set lastmgr + $szLastMessage = $logArray[SYSLOG_MESSAGE]; + else + { + // Skip if same msg + if ( $szLastMessage == $logArray[SYSLOG_MESSAGE] ) + { + // Set last mgr + $szLastMessage = $logArray[SYSLOG_MESSAGE]; + + // Skip entry + continue; + } + } + } + // --- + // --- Set CSS Class if ( $counter % 2 == 0 ) $content['syslogmessages'][$counter]['cssclass'] = "line1"; diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index 59c988a..115e7ad 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -77,6 +77,7 @@ $content['LN_GEN_CONFIGFILE'] = "Configuration File"; $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; +$content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 877b34c..115e7ad 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -77,7 +77,7 @@ $content['LN_GEN_CONFIGFILE'] = "Configuration File"; $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; - +$content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index 59c988a..115e7ad 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -77,6 +77,7 @@ $content['LN_GEN_CONFIGFILE'] = "Configuration File"; $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; +$content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 4b124a8..1fc7f04 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -93,7 +93,10 @@ {LN_GEN_IPADRRESOLVE}
{LN_GEN_SUPPRESSDUPMSG}
{LN_ADMIN_MISC}
"); print(""); diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 79a2933..ab801e4 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -125,7 +125,7 @@ function CreateUserName( $username, $password, $is_admin ) function CheckUserLogin( $username, $password ) { - global $content, $CFG; + global $content; // TODO: SessionTime and AccessLevel check @@ -180,7 +180,7 @@ function CheckUserLogin( $username, $password ) } else { - if ( $CFG['DebugUserLogin'] == 1 ) + if ( GetConfigSetting("DebugUserLogin", 0) == 1 ) DieWithFriendlyErrorMsg( "Debug Error: Could not login user '" . $username . "'

Sessionarray
" . var_export($_SESSION, true) . "

SQL Statement: " . $sqlselect ); // Default return false diff --git a/src/index.php b/src/index.php index 7e86e68..8ab2091 100644 --- a/src/index.php +++ b/src/index.php @@ -300,7 +300,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c { // --- Extra stuff for suppressing messages if ( - isset($CFG['SuppressDuplicatedMessages']) && $CFG['SuppressDuplicatedMessages'] == 1 + GetConfigSetting("SuppressDuplicatedMessages", 0, CFGLEVEL_USER) == 1 && isset($logArray[SYSLOG_MESSAGE]) ) @@ -474,7 +474,10 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c // Set truncasted message for display if ( isset($logArray[SYSLOG_MESSAGE]) ) { - $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[SYSLOG_MESSAGE]) > $CFG['ViewMessageCharacterLimit'] ? substr($logArray[SYSLOG_MESSAGE], 0, $CFG['ViewMessageCharacterLimit'] ) . " ..." : $logArray[SYSLOG_MESSAGE]); + // Copy tmp + $tmpCharLimit = GetConfigSetting("ViewMessageCharacterLimit", 80, CFGLEVEL_USER); + + $content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes(strlen($logArray[SYSLOG_MESSAGE]) > $tmpCharLimit ? substr($logArray[SYSLOG_MESSAGE], 0, $tmpCharLimit) . " ..." : $logArray[SYSLOG_MESSAGE]); // Enable LINK property! for this field $content['syslogmessages'][$counter]['values'][$mycolkey]['ismessagefield'] = true; @@ -493,7 +496,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c AddContextLinks($content['syslogmessages'][$counter]['values'][$mycolkey]['fieldvalue']); // --- - if ( isset($CFG['ViewEnableDetailPopups']) && $CFG['ViewEnableDetailPopups'] == 1 ) + if ( GetConfigSetting("ViewEnableDetailPopups", 0, CFGLEVEL_USER) ) { $content['syslogmessages'][$counter]['values'][$mycolkey]['popupcaption'] = GetAndReplaceLangStr( $content['LN_GRID_POPUPDETAILS'], $logArray[SYSLOG_UID]); $content['syslogmessages'][$counter]['values'][$mycolkey]['hasdetails'] = "true"; From 99008ee360eb43399658bb8d84d5a4c1cc58c5fc Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 30 Jul 2008 16:18:02 +0200 Subject: [PATCH 042/142] Normal Users and Admins can now optionally customize most of the general options. phpLogCon will priorize the users personal options, and only use the global options in a second step. --- src/admin/index.php | 241 ++++++++++++++++++++++----- src/include/functions_common.php | 55 ++++-- src/include/functions_db.php | 93 ++++++++--- src/include/functions_users.php | 22 ++- src/lang/en/admin.php | 6 + src/templates/admin/admin_index.html | 201 ++++++++++++++++------ 6 files changed, 486 insertions(+), 132 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index 53cff38..c9db17f 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -56,57 +56,143 @@ InitFilterHelpers(); // Helpers for frontend filtering! IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); // --- BEGIN Custom Code - if ( isset($_SESSION['SESSION_ISADMIN']) && $_SESSION['SESSION_ISADMIN'] == 1 ) +{ $content['EditAllowed'] = true; + $content['DISABLE_GLOBALEDIT_FORMCONTROL'] = ""; +} else +{ $content['EditAllowed'] = false; + $content['DISABLE_GLOBALEDIT_FORMCONTROL'] = "disabled"; +} + +// --- First thing to do is to check the op get parameter! +// Check for changes first | Abort if Edit is not allowed +if ( isset($_GET['op']) && isset($_GET['value']) ) +{ + if ( $_GET['op'] == "enableuserops" ) + { + $iNewVal = intval($_GET['value']); + if ( $iNewVal == 1 ) + $USERCFG['UserOverwriteOptions'] = 1; + else + $USERCFG['UserOverwriteOptions'] = 0; + + // Enable User Options! + WriteConfigValue( "UserOverwriteOptions", false, $content['SESSION_USERID'] ); + } +} +// --- + +// --- Check if user wants to overwrite +$UserOverwriteOptions = GetConfigSetting("UserOverwriteOptions", 0, CFGLEVEL_USER); +if ( $UserOverwriteOptions == 1 ) +{ + $content['ENABLEUSEROPTIONS'] = true; +} +else +{ + $content['ENABLEUSEROPTIONS'] = false; + + +} +// --- // Check for changes first | Abort if Edit is not allowed -if ( isset($_POST['op']) && $content['EditAllowed'] ) +if ( isset($_POST['op']) ) { if ( $_POST['op'] == "edit" ) { - // Language needs special treatment - if ( isset ($_POST['ViewDefaultLanguage']) ) - { - $tmpvar = DB_RemoveBadChars($_POST['ViewDefaultLanguage']); - if ( VerifyLanguage($tmpvar) ) - $content['ViewDefaultLanguage'] = $tmpvar; + // Do if User is ADMIN + if ( $content['EditAllowed'] ) + { + // Language needs special treatment + if ( isset ($_POST['ViewDefaultLanguage']) ) + { + $tmpvar = DB_RemoveBadChars($_POST['ViewDefaultLanguage']); + if ( VerifyLanguage($tmpvar) ) + $content['ViewDefaultLanguage'] = $tmpvar; + } + + // Read default theme + if ( isset ($_POST['ViewDefaultTheme']) ) { $content['ViewDefaultTheme'] = $_POST['ViewDefaultTheme']; } + + // Read default VIEW | Check if View exists as well! + if ( isset ($_POST['DefaultViewsID']) && isset($content['Views'][$_POST['DefaultViewsID']] )) { $content['DefaultViewsID'] = $_POST['DefaultViewsID']; } + + // Read default SOURCES | Check if Source exists as well! + if ( isset ($_POST['DefaultSourceID']) && isset($content['Sources'][$_POST['DefaultSourceID']] )) { $content['DefaultSourceID'] = $_POST['DefaultSourceID']; } + + // Read checkboxes + if ( isset ($_POST['ViewUseTodayYesterday']) ) { $content['ViewUseTodayYesterday'] = 1; } else { $content['ViewUseTodayYesterday'] = 0; } + if ( isset ($_POST['ViewEnableDetailPopups']) ) { $content['ViewEnableDetailPopups'] = 1; } else { $content['ViewEnableDetailPopups'] = 0; } + if ( isset ($_POST['EnableIPAddressResolve']) ) { $content['EnableIPAddressResolve'] = 1; } else { $content['EnableIPAddressResolve'] = 0; } + if ( isset ($_POST['MiscShowDebugMsg']) ) { $content['MiscShowDebugMsg'] = 1; } else { $content['MiscShowDebugMsg'] = 0; } + if ( isset ($_POST['MiscShowDebugGridCounter']) ) { $content['MiscShowDebugGridCounter'] = 1; } else { $content['MiscShowDebugGridCounter'] = 0; } + if ( isset ($_POST['MiscShowPageRenderStats']) ) { $content['MiscShowPageRenderStats'] = 1; } else { $content['MiscShowPageRenderStats'] = 0; } + if ( isset ($_POST['MiscEnableGzipCompression']) ) { $content['MiscEnableGzipCompression'] = 1; } else { $content['MiscEnableGzipCompression'] = 0; } + if ( isset ($_POST['DebugUserLogin']) ) { $content['DebugUserLogin'] = 1; } else { $content['DebugUserLogin'] = 0; } + if ( isset ($_POST['SuppressDuplicatedMessages']) ) { $content['SuppressDuplicatedMessages'] = 1; } else { $content['SuppressDuplicatedMessages'] = 0; } + + // Read Text number fields + if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_POST['ViewMessageCharacterLimit']; } + if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = $_POST['ViewEntriesPerPage']; } + if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = $_POST['ViewEnableAutoReloadSeconds']; } + + // Read Text fields + if ( isset ($_POST['PrependTitle']) ) { $content['PrependTitle'] = $_POST['PrependTitle']; } + if ( isset ($_POST['SearchCustomButtonCaption']) ) { $content['SearchCustomButtonCaption'] = $_POST['SearchCustomButtonCaption']; } + if ( isset ($_POST['SearchCustomButtonSearch']) ) { $content['SearchCustomButtonSearch'] = $_POST['SearchCustomButtonSearch']; } + + // Save configuration variables now + SaveGeneralSettingsIntoDB(); } + + // Do if User wants extra options + if ( $content['ENABLEUSEROPTIONS'] ) + { + // Language needs special treatment + if ( isset ($_POST['User_ViewDefaultLanguage']) ) + { + $tmpvar = DB_RemoveBadChars($_POST['User_ViewDefaultLanguage']); + if ( VerifyLanguage($tmpvar) ) + $USERCFG['ViewDefaultLanguage'] = $tmpvar; + } - // Read default theme - if ( isset ($_POST['ViewDefaultTheme']) ) { $content['ViewDefaultTheme'] = $_POST['ViewDefaultTheme']; } + // Read default theme + if ( isset ($_POST['User_ViewDefaultTheme']) ) { $USERCFG['ViewDefaultTheme'] = $_POST['User_ViewDefaultTheme']; } - // Read default VIEW | Check if View exists as well! - if ( isset ($_POST['DefaultViewsID']) && isset($content['Views'][$_POST['DefaultViewsID']] )) { $content['DefaultViewsID'] = $_POST['DefaultViewsID']; } + // Read default VIEW | Check if View exists as well! + if ( isset ($_POST['User_DefaultViewsID']) && isset($content['Views'][$_POST['User_DefaultViewsID']] )) { $USERCFG['DefaultViewsID'] = $_POST['User_DefaultViewsID']; } - // Read default SOURCES | Check if Source exists as well! - if ( isset ($_POST['DefaultSourceID']) && isset($content['Sources'][$_POST['DefaultSourceID']] )) { $content['DefaultSourceID'] = $_POST['DefaultSourceID']; } + // Read default SOURCES | Check if Source exists as well! + if ( isset ($_POST['User_DefaultSourceID']) && isset($content['Sources'][$_POST['User_DefaultSourceID']] )) { $USERCFG['DefaultSourceID'] = $_POST['User_DefaultSourceID']; } - // Read checkboxes - if ( isset ($_POST['ViewUseTodayYesterday']) ) { $content['ViewUseTodayYesterday'] = 1; } else { $content['ViewUseTodayYesterday'] = 0; } - if ( isset ($_POST['ViewEnableDetailPopups']) ) { $content['ViewEnableDetailPopups'] = 1; } else { $content['ViewEnableDetailPopups'] = 0; } - if ( isset ($_POST['EnableIPAddressResolve']) ) { $content['EnableIPAddressResolve'] = 1; } else { $content['EnableIPAddressResolve'] = 0; } - if ( isset ($_POST['MiscShowDebugMsg']) ) { $content['MiscShowDebugMsg'] = 1; } else { $content['MiscShowDebugMsg'] = 0; } - if ( isset ($_POST['MiscShowDebugGridCounter']) ) { $content['MiscShowDebugGridCounter'] = 1; } else { $content['MiscShowDebugGridCounter'] = 0; } - if ( isset ($_POST['MiscShowPageRenderStats']) ) { $content['MiscShowPageRenderStats'] = 1; } else { $content['MiscShowPageRenderStats'] = 0; } - if ( isset ($_POST['MiscEnableGzipCompression']) ) { $content['MiscEnableGzipCompression'] = 1; } else { $content['MiscEnableGzipCompression'] = 0; } - if ( isset ($_POST['DebugUserLogin']) ) { $content['DebugUserLogin'] = 1; } else { $content['DebugUserLogin'] = 0; } - if ( isset ($_POST['SuppressDuplicatedMessages']) ) { $content['SuppressDuplicatedMessages'] = 1; } else { $content['SuppressDuplicatedMessages'] = 0; } + // Read checkboxes + if ( isset ($_POST['User_ViewUseTodayYesterday']) ) { $USERCFG['ViewUseTodayYesterday'] = 1; } else { $USERCFG['ViewUseTodayYesterday'] = 0; } + if ( isset ($_POST['User_ViewEnableDetailPopups']) ) { $USERCFG['ViewEnableDetailPopups'] = 1; } else { $USERCFG['ViewEnableDetailPopups'] = 0; } + if ( isset ($_POST['User_EnableIPAddressResolve']) ) { $USERCFG['EnableIPAddressResolve'] = 1; } else { $USERCFG['EnableIPAddressResolve'] = 0; } + if ( isset ($_POST['User_MiscShowDebugMsg']) ) { $USERCFG['MiscShowDebugMsg'] = 1; } else { $USERCFG['MiscShowDebugMsg'] = 0; } + if ( isset ($_POST['User_MiscShowDebugGridCounter']) ) { $USERCFG['MiscShowDebugGridCounter'] = 1; } else { $USERCFG['MiscShowDebugGridCounter'] = 0; } + if ( isset ($_POST['User_MiscShowPageRenderStats']) ) { $USERCFG['MiscShowPageRenderStats'] = 1; } else { $USERCFG['MiscShowPageRenderStats'] = 0; } + if ( isset ($_POST['User_MiscEnableGzipCompression']) ) { $USERCFG['MiscEnableGzipCompression'] = 1; } else { $USERCFG['MiscEnableGzipCompression'] = 0; } +// DISABLED FOR USER! if ( isset ($_POST['User_DebugUserLogin']) ) { $USERCFG['DebugUserLogin'] = 1; } else { $USERCFG['DebugUserLogin'] = 0; } + if ( isset ($_POST['User_SuppressDuplicatedMessages']) ) { $USERCFG['SuppressDuplicatedMessages'] = 1; } else { $USERCFG['SuppressDuplicatedMessages'] = 0; } - // Read Text number fields - if ( isset ($_POST['ViewMessageCharacterLimit']) && is_numeric($_POST['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_POST['ViewMessageCharacterLimit']; } - if ( isset ($_POST['ViewEntriesPerPage']) && is_numeric($_POST['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = $_POST['ViewEntriesPerPage']; } - if ( isset ($_POST['ViewEnableAutoReloadSeconds']) && is_numeric($_POST['ViewEnableAutoReloadSeconds']) ) { $content['ViewEnableAutoReloadSeconds'] = $_POST['ViewEnableAutoReloadSeconds']; } + // Read Text number fields + if ( isset ($_POST['User_ViewMessageCharacterLimit']) && is_numeric($_POST['User_ViewMessageCharacterLimit']) ) { $USERCFG['ViewMessageCharacterLimit'] = $_POST['User_ViewMessageCharacterLimit']; } + if ( isset ($_POST['User_ViewEntriesPerPage']) && is_numeric($_POST['User_ViewEntriesPerPage']) ) { $USERCFG['ViewEntriesPerPage'] = $_POST['User_ViewEntriesPerPage']; } + if ( isset ($_POST['User_ViewEnableAutoReloadSeconds']) && is_numeric($_POST['User_ViewEnableAutoReloadSeconds']) ) { $USERCFG['ViewEnableAutoReloadSeconds'] = $_POST['User_ViewEnableAutoReloadSeconds']; } - // Read Text fields - if ( isset ($_POST['PrependTitle']) ) { $content['PrependTitle'] = $_POST['PrependTitle']; } - if ( isset ($_POST['SearchCustomButtonCaption']) ) { $content['SearchCustomButtonCaption'] = $_POST['SearchCustomButtonCaption']; } - if ( isset ($_POST['SearchCustomButtonSearch']) ) { $content['SearchCustomButtonSearch'] = $_POST['SearchCustomButtonSearch']; } + // Read Text fields + if ( isset ($_POST['User_PrependTitle']) ) { $USERCFG['PrependTitle'] = $_POST['User_PrependTitle']; } + if ( isset ($_POST['User_SearchCustomButtonCaption']) ) { $USERCFG['SearchCustomButtonCaption'] = $_POST['User_SearchCustomButtonCaption']; } + if ( isset ($_POST['User_SearchCustomButtonSearch']) ) { $USERCFG['SearchCustomButtonSearch'] = $_POST['User_SearchCustomButtonSearch']; } - // Save configuration variables now - SaveGeneralSettingsIntoDB(); + // Save configuration variables now + SaveUserGeneralSettingsIntoDB(); + } // Do a redirect RedirectResult( $content['LN_GEN_SUCCESSFULLYSAVED'], "index.php" ); @@ -141,18 +227,95 @@ foreach ( $content['VIEWS'] as $myView ) // --- // --- Init for DefaultSource field! -// copy Views Array +// copy Sources Array $content['SOURCES'] = $content['Sources']; if ( !isset($content['DefaultSourceID']) ) { $content['DefaultSourceID'] = ''; } -foreach ( $content['SOURCES'] as $myView ) +foreach ( $content['SOURCES'] as $mySource ) { - if ( $myView['ID'] == $content['DefaultSourceID'] ) - $content['SOURCES'][ $myView['ID'] ]['selected'] = "selected"; + if ( $mySource['ID'] == $content['DefaultSourceID'] ) + $content['SOURCES'][ $mySource['ID'] ]['selected'] = "selected"; else - $content['SOURCES'][ $myView['ID'] ]['selected'] = ""; + $content['SOURCES'][ $mySource['ID'] ]['selected'] = ""; } // --- +// Do if User wants extra options +if ( $content['ENABLEUSEROPTIONS'] ) +{ + // Set checkbox States + if ( GetConfigSetting('ViewUseTodayYesterday', $content['ViewUseTodayYesterday'], CFGLEVEL_USER) == 1) { $content['User_ViewUseTodayYesterday_checked'] = "checked"; } else { $content['User_ViewUseTodayYesterday_checked'] = ""; } + if ( GetConfigSetting('ViewEnableDetailPopups', $content['ViewEnableDetailPopups'], CFGLEVEL_USER) == 1) { $content['User_ViewEnableDetailPopups_checked'] = "checked"; } else { $content['User_ViewEnableDetailPopups_checked'] = ""; } + if ( GetConfigSetting('EnableIPAddressResolve', $content['EnableIPAddressResolve'], CFGLEVEL_USER) == 1) { $content['User_EnableIPAddressResolve_checked'] = "checked"; } else { $content['User_EnableIPAddressResolve_checked'] = ""; } + + if ( GetConfigSetting('MiscShowDebugMsg', $content['MiscShowDebugMsg'], CFGLEVEL_USER) == 1) { $content['User_MiscShowDebugMsg_checked'] = "checked"; } else { $content['User_MiscShowDebugMsg_checked'] = ""; } + if ( GetConfigSetting('MiscShowDebugGridCounter', $content['MiscShowDebugGridCounter'], CFGLEVEL_USER) == 1) { $content['User_MiscShowDebugGridCounter_checked'] = "checked"; } else { $content['User_MiscShowDebugGridCounter_checked'] = ""; } + if ( GetConfigSetting('MiscShowPageRenderStats', $content['MiscShowPageRenderStats'], CFGLEVEL_USER) == 1) { $content['User_MiscShowPageRenderStats_checked'] = "checked"; } else { $content['User_MiscShowPageRenderStats_checked'] = ""; } + if ( GetConfigSetting('MiscEnableGzipCompression', $content['MiscEnableGzipCompression'], CFGLEVEL_USER) == 1) { $content['User_MiscEnableGzipCompression_checked'] = "checked"; } else { $content['User_MiscEnableGzipCompression_checked'] = ""; } + if ( GetConfigSetting('SuppressDuplicatedMessages', $content['SuppressDuplicatedMessages'], CFGLEVEL_USER) == 1) { $content['User_SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['User_SuppressDuplicatedMessages_checked'] = ""; } + // --- + + // --- Set TextFields! + $content['User_PrependTitle'] = GetConfigSetting('PrependTitle', $content['PrependTitle'], CFGLEVEL_USER); + $content['User_ViewMessageCharacterLimit'] = GetConfigSetting('ViewMessageCharacterLimit', $content['ViewMessageCharacterLimit'], CFGLEVEL_USER); + $content['User_ViewEntriesPerPage'] = GetConfigSetting('ViewEntriesPerPage', $content['ViewEntriesPerPage'], CFGLEVEL_USER); + $content['User_ViewEnableAutoReloadSeconds'] = GetConfigSetting('ViewEnableAutoReloadSeconds', $content['ViewEnableAutoReloadSeconds'], CFGLEVEL_USER); + $content['User_SearchCustomButtonCaption'] = GetConfigSetting('SearchCustomButtonCaption', $content['SearchCustomButtonCaption'], CFGLEVEL_USER); + $content['User_SearchCustomButtonSearch'] = GetConfigSetting('SearchCustomButtonSearch', $content['SearchCustomButtonSearch'], CFGLEVEL_USER); + // --- + + // --- Init for ViewDefaultTheme field! + // copy STYLES Array + $content['USER_STYLES'] = $content['STYLES']; + $userStyleID = GetConfigSetting('ViewDefaultTheme', $content['ViewDefaultTheme'], CFGLEVEL_USER); + foreach ( $content['USER_STYLES'] as &$myStyle ) + { + if ( $myStyle['StyleName'] == $userStyleID ) + $myStyle['selected'] = "selected"; + else + $myStyle['selected'] = ""; + } + // --- + + // --- Init for ViewDefaultLanguage field! + // copy LANGUAGES Array + $content['USER_LANGUAGES'] = $content['LANGUAGES']; + $userLangID = GetConfigSetting('ViewDefaultLanguage', $content['ViewDefaultLanguage'], CFGLEVEL_USER); + foreach ( $content['USER_LANGUAGES'] as &$myLang ) + { + if ( $myLang['langcode'] == $userLangID ) + $myLang['selected'] = "selected"; + else + $myLang['selected'] = ""; + } + // --- + + // --- Init for DefaultView field! + // copy Views Array + $content['USER_VIEWS'] = $content['Views']; + $userViewID = GetConfigSetting('DefaultViewsID', $content['DefaultViewsID'], CFGLEVEL_USER); + foreach ( $content['USER_VIEWS'] as &$myView ) + { + if ( $myView['ID'] == $userViewID ) + $myView['selected'] = "selected"; + else + $myView['selected'] = ""; + } + // --- + + // --- Init for DefaultSource field! + // copy Sources Array + $content['USER_SOURCES'] = $content['Sources']; + $userSourceID = GetConfigSetting('DefaultViewsID', $content['DefaultViewsID'], CFGLEVEL_USER); + foreach ( $content['USER_SOURCES'] as &$mySource ) + { + if ( $mySource['ID'] == $userSourceID ) + $mySource['selected'] = "selected"; + else + $mySource['selected'] = ""; + } + // --- +} + // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); $content['TITLE'] .= " :: " . $content['LN_ADMINMENU_GENOPT']; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index bb0a209..85b5cc9 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -146,9 +146,6 @@ function InitPhpLogCon() // Init UserDB related stuff! InitUserSystemPhpLogCon(); - // Moved here, because we do not need if GZIP needs to be enabled before the config is loaded! - InitRuntimeInformations(); - // Establish DB Connection if ( GetConfigSetting("UserDBEnabled", false) ) DB_Connect(); @@ -156,6 +153,9 @@ function InitPhpLogCon() // Now load the Page configuration values InitConfigurationValues(); + // Moved here, because we do not need if GZIP needs to be enabled before the config is loaded! + InitRuntimeInformations(); + // Now Create Themes List because we haven't the config before! CreateThemesList(); @@ -376,8 +376,6 @@ function InitRuntimeInformations() { global $content; - // TODO| maybe not needed! - // Enable GZIP Compression if enabled! if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false && GetConfigSetting("MiscEnableGzipCompression", 1, CFGLEVEL_USER) == 1 ) { @@ -445,7 +443,8 @@ function InitFrontEndVariables() $content['MENU_SOURCE_PDO'] = $content['BASEPATH'] . "images/icons/data_gear.png"; $content['MENU_MAXIMIZE'] = $content['BASEPATH'] . "images/icons/table_selection_all.png"; $content['MENU_NORMAL'] = $content['BASEPATH'] . "images/icons/table_selection_block.png"; - + $content['MENU_USEROPTIONS'] = $content['BASEPATH'] . "images/icons/businessman_preferences.png"; + $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; $content['MENU_PAGER_NEXT'] = $content['BASEPATH'] . "images/icons/media_fast_forward.png"; @@ -500,7 +499,7 @@ function InitConfigurationValues() if ( $result ) { - $rows = DB_GetAllRows($result, true, true); + $rows = DB_GetAllRows($result, true); // Read results from DB and overwrite in $CFG Array! if ( isset($rows ) ) { @@ -704,7 +703,7 @@ function InitPageTitle() { global $content, $currentSourceID; - $tmpTitle = GetConfigSetting("PrependTitle", ""); + $tmpTitle = GetConfigSetting("PrependTitle", "", CFGLEVEL_USER); if ( strlen($tmpTitle) > 0 ) $szReturn = $tmpTitle . " :: "; else @@ -1079,15 +1078,51 @@ function SaveGeneralSettingsIntoDB() WriteConfigValue( "DefaultSourceID", true ); } +function SaveUserGeneralSettingsIntoDB() +{ + global $content; + + WriteConfigValue( "ViewDefaultLanguage", false, $content['SESSION_USERID']); + WriteConfigValue( "ViewDefaultTheme", false, $content['SESSION_USERID'] ); + + WriteConfigValue( "ViewUseTodayYesterday", false, $content['SESSION_USERID'] ); + WriteConfigValue( "ViewEnableDetailPopups", false, $content['SESSION_USERID'] ); + WriteConfigValue( "EnableIPAddressResolve", false, $content['SESSION_USERID'] ); + WriteConfigValue( "MiscShowDebugMsg", false, $content['SESSION_USERID'] ); + WriteConfigValue( "MiscShowDebugGridCounter", false, $content['SESSION_USERID'] ); + WriteConfigValue( "MiscShowPageRenderStats", false, $content['SESSION_USERID'] ); + WriteConfigValue( "MiscEnableGzipCompression", false, $content['SESSION_USERID'] ); + WriteConfigValue( "SuppressDuplicatedMessages", false, $content['SESSION_USERID'] ); + + WriteConfigValue( "ViewMessageCharacterLimit", false, $content['SESSION_USERID'] ); + WriteConfigValue( "ViewEntriesPerPage", false, $content['SESSION_USERID'] ); + WriteConfigValue( "ViewEnableAutoReloadSeconds", false, $content['SESSION_USERID'] ); + + WriteConfigValue( "PrependTitle", false, $content['SESSION_USERID'] ); + WriteConfigValue( "SearchCustomButtonCaption", false, $content['SESSION_USERID'] ); + WriteConfigValue( "SearchCustomButtonSearch", false, $content['SESSION_USERID'] ); + + // Extra Fields + WriteConfigValue( "DefaultViewsID", false, $content['SESSION_USERID'] ); + WriteConfigValue( "DefaultSourceID", false, $content['SESSION_USERID'] ); +} + + function GetConfigSetting($szSettingName, $szDefaultValue = "", $DesiredConfigLevel = CFGLEVEL_GLOBAL) { - global $content, $CFG; + global $content, $CFG, $USERCFG; if ( isset($CFG['UserDBEnabled']) && $CFG['UserDBEnabled'] ) { if ( $DesiredConfigLevel == CFGLEVEL_USER ) { - // TODO! + // only use user settings if desired by the user + if ( isset($USERCFG['UserOverwriteOptions']) && $USERCFG['UserOverwriteOptions'] == 1 ) + { + // return user specific setting if available + if ( isset($USERCFG[$szSettingName]) ) + return $USERCFG[$szSettingName]; + } } } diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 5b4a383..2bb2c17 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -320,38 +320,77 @@ function WriteConfigValue($szPropName, $is_global = true, $userid = false, $grou return; // --- - // !!! TODO HANDLE USER AND GROUP FIELDS! - - if ( isset($content[$szPropName]) ) + if ( $is_global ) { - // Copy value for DB and check for BadDB Chars! - $szDbValue = PrepareValueForDB( $content[$szPropName] ); - } - else - { - // Set empty in this case - $szDbValue = ""; - $content[$szPropName] = ""; - } + if ( isset($content[$szPropName]) ) + { + // Copy value for DB and check for BadDB Chars! + $szDbValue = PrepareValueForDB( $content[$szPropName] ); + } + else + { + // Set empty in this case + $szDbValue = ""; + $content[$szPropName] = ""; + } - // Copy to $CFG array as well - $CFG[$szPropName] = $content[$szPropName]; - - // Check if we need to INSERT or UPDATE - $result = DB_Query("SELECT propname FROM " . DB_CONFIG . " WHERE propname = '" . $szPropName . "' AND is_global = " . $is_global); - $rows = DB_GetAllRows($result, true); - if ( !isset($rows) ) - { - // New Entry - $result = DB_Query("INSERT INTO " . DB_CONFIG . " (propname, propvalue, is_global) VALUES ( '" . $szPropName . "', '" . $szDbValue . "', " . $is_global . ")"); - DB_FreeQuery($result); + // Copy to $CFG array as well + $CFG[$szPropName] = $content[$szPropName]; + + // Check if we need to INSERT or UPDATE + $result = DB_Query("SELECT propname FROM " . DB_CONFIG . " WHERE propname = '" . $szPropName . "' AND is_global = " . $is_global); + $rows = DB_GetAllRows($result, true); + if ( !isset($rows) ) + { + // New Entry + $result = DB_Query("INSERT INTO " . DB_CONFIG . " (propname, propvalue, is_global) VALUES ( '" . $szPropName . "', '" . $szDbValue . "', " . $is_global . ")"); + DB_FreeQuery($result); + } + else + { + // Update Entry + $result = DB_Query("UPDATE " . DB_CONFIG . " SET propvalue = '" . $szDbValue . "' WHERE propname = '" . $szPropName . "' AND is_global = " . $is_global); + DB_FreeQuery($result); + } } - else + else if ( $userid != false ) { - // Update Entry - $result = DB_Query("UPDATE " . DB_CONFIG . " SET propvalue = '" . $szDbValue . "' WHERE propname = '" . $szPropName . "' AND is_global = " . $is_global); - DB_FreeQuery($result); + global $USERCFG; + + if ( isset($USERCFG[$szPropName]) ) + { + // Copy value for DB and check for BadDB Chars! + $szDbValue = PrepareValueForDB( $USERCFG[$szPropName] ); + } + else + { + // Set empty in this case + $szDbValue = ""; + $USERCFG[$szPropName] = ""; + } + + // Check if we need to INSERT or UPDATE + $result = DB_Query("SELECT propname FROM " . DB_CONFIG . " WHERE propname = '" . $szPropName . "' AND userid = " . $userid); + $rows = DB_GetAllRows($result, true); + if ( !isset($rows) ) + { + // New Entry + $result = DB_Query("INSERT INTO " . DB_CONFIG . " (propname, propvalue, userid) VALUES ( '" . $szPropName . "', '" . $szDbValue . "', " . $userid . ")"); + DB_FreeQuery($result); + } + else + { + // Update Entry + $result = DB_Query("UPDATE " . DB_CONFIG . " SET propvalue = '" . $szDbValue . "' WHERE propname = '" . $szPropName . "' AND userid = " . $userid); + DB_FreeQuery($result); + } + } + else if ( $groupid != false ) + DieWithFriendlyErrorMsg( "Critical Error occured in WriteConfigValue, writing GROUP specific properties is not supported yet!" ); + + + } function GetSingleDBEntryOnly( $myqry ) diff --git a/src/include/functions_users.php b/src/include/functions_users.php index ab801e4..7597760 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -52,7 +52,7 @@ $content['IS_USERSYSTEMENABLED'] = true; // --- BEGIN Usermanagement Function --- function InitUserSession() { - global $content; + global $USERCFG, $content; // --- Hide donate Button if not on Admin Page if ( !defined('IS_ADMINPAGE') ) @@ -77,6 +77,26 @@ function InitUserSession() $content['SESSION_ISADMIN'] = $_SESSION['SESSION_ISADMIN']; if ( isset($_SESSION['SESSION_GROUPIDS']) ) $content['SESSION_GROUPIDS'] = $_SESSION['SESSION_GROUPIDS']; + + // --- Now we obtain user specific general settings from the DB for the user! + $result = DB_Query("SELECT * FROM " . DB_CONFIG . " WHERE userid = " . $content['SESSION_USERID']); + if ( $result ) + { + $rows = DB_GetAllRows($result, true); + // Read results from DB and overwrite in $CFG Array! + if ( isset($rows ) ) + { + for($i = 0; $i < count($rows); $i++) + { + // Store and overwrite settings from the user here! + $USERCFG[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; +// $content[ $rows[$i]['propname'] ] = $rows[$i]['propvalue']; + } + } + } + else // Critical ERROR HERE! + DieWithFriendlyErrorMsg( "Critical Error occured while trying to access the database in table '" . DB_CONFIG . "'" ); + // --- // Successfully logged in return true; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 115e7ad..4822fa0 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -78,6 +78,12 @@ $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; $content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; + $content['LN_GEN_OPTIONNAME'] = "Option name"; + $content['LN_GEN_GLOBALVALUE'] = "Global value"; + $content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; + $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; + $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; + // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 1fc7f04..34807c2 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -5,132 +5,223 @@
Debugmessage: {LN_ADMINMENU_GENOPT}
-

- +
- +
- + + + + + + + + - - + + + + - - + + + + - - + + + + - - + + + + - - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - - + + + + + - - + + + + + - - + + + + + - - + + + + + - - + + + + + -
+   + + {LN_GEN_DISABLEUSEROPTIONS} +
+ + + {LN_GEN_ENABLEUSEROPTIONS} +
+ + {LN_GEN_PERSONALVALUE} +
{LN_GEN_OPTIONNAME}{LN_GEN_GLOBALVALUE}
{LN_ADMIN_GLOBFRONTEND}
{LN_GEN_WEBSTYLE} - {LN_GEN_WEBSTYLE} + + +
{LN_GEN_SELLANGUAGE} - {LN_GEN_SELLANGUAGE} + + +
{LN_GEN_DEFVIEWS} - {LN_GEN_DEFVIEWS} + + +
{LN_GEN_DEFSOURCE} - {LN_GEN_DEFSOURCE} + + +
{LN_GEN_PREPENDTITLE}{LN_GEN_PREPENDTITLE}
{LN_GEN_MSGCHARLIMIT}{LN_GEN_MSGCHARLIMIT}
{LN_GEN_ENTRIESPERPAGE}{LN_GEN_ENTRIESPERPAGE}
{LN_GEN_AUTORELOADSECONDS}{LN_GEN_AUTORELOADSECONDS}
{LN_GEN_CUSTBTNCAPT}{LN_GEN_CUSTBTNCAPT}
{LN_GEN_CUSTBTNSRCH}{LN_GEN_CUSTBTNSRCH}
{LN_GEN_USETODAY}{LN_GEN_USETODAY}
{LN_GEN_DETAILPOPUPS}{LN_GEN_DETAILPOPUPS}
{LN_GEN_IPADRRESOLVE}{LN_GEN_IPADRRESOLVE}
{LN_GEN_SUPPRESSDUPMSG}{LN_GEN_SUPPRESSDUPMSG}
+ {LN_ADMIN_MISC}
{LN_GEN_SHOWDEBUGMSG}{LN_GEN_SHOWDEBUGMSG}
{LN_GEN_DEBUGGRIDCOUNTER}{LN_GEN_DEBUGGRIDCOUNTER}
{LN_GEN_SHOWPAGERENDERSTATS}{LN_GEN_SHOWPAGERENDERSTATS}
{LN_GEN_ENABLEGZIP}{LN_GEN_ENABLEGZIP}
{LN_GEN_DEBUGUSERLOGIN}{LN_GEN_DEBUGUSERLOGIN} 
- + - - - - -
From 984e670d740fe51f92c3d10743c7fb3528be0196 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 31 Jul 2008 12:04:19 +0200 Subject: [PATCH 043/142] Added language string into other lang files and fixed minor bugs in the admin center general options --- src/admin/index.php | 42 +++++++++++++++++++++++----- src/lang/de/admin.php | 6 ++++ src/lang/en/admin.php | 10 +++---- src/lang/pt_BR/admin.php | 5 ++++ src/templates/admin/admin_index.html | 8 +++--- src/templates/admin/result.html | 3 ++ 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index c9db17f..6a277af 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -213,16 +213,44 @@ if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "che if ($content['SuppressDuplicatedMessages'] == 1) { $content['SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['SuppressDuplicatedMessages_checked'] = ""; } // --- +// --- Init for Style field! + +// copy STYLES Array +$content['GLOBAL_STYLES'] = $content['STYLES']; +$defaultStyleID = GetConfigSetting('ViewDefaultTheme', "default", CFGLEVEL_GLOBAL); +foreach ( $content['GLOBAL_STYLES'] as &$myStyle ) +{ + if ( $myStyle['StyleName'] == $defaultStyleID ) + $myStyle['selected'] = "selected"; + else + $myStyle['selected'] = ""; +} +// --- + +// --- Init for ViewDefaultLanguage field! +// copy LANGUAGES Array +$content['GLOBAL_LANGUAGES'] = $content['LANGUAGES']; + +$defaultLangID = GetConfigSetting('ViewDefaultLanguage', "en", CFGLEVEL_GLOBAL); +foreach ( $content['GLOBAL_LANGUAGES'] as &$myLang ) +{ + if ( $myLang['langcode'] == $defaultLangID ) + $myLang['selected'] = "selected"; + else + $myLang['selected'] = ""; +} +// --- + // --- Init for DefaultView field! // copy Views Array $content['VIEWS'] = $content['Views']; if ( !isset($content['DefaultViewsID']) ) { $content['DefaultViewsID'] = 'SYSLOG'; } -foreach ( $content['VIEWS'] as $myView ) +foreach ( $content['VIEWS'] as &$myView ) { if ( $myView['ID'] == $content['DefaultViewsID'] ) - $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + $myView['selected'] = "selected"; else - $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + $myView['selected'] = ""; } // --- @@ -230,12 +258,12 @@ foreach ( $content['VIEWS'] as $myView ) // copy Sources Array $content['SOURCES'] = $content['Sources']; if ( !isset($content['DefaultSourceID']) ) { $content['DefaultSourceID'] = ''; } -foreach ( $content['SOURCES'] as $mySource ) +foreach ( $content['SOURCES'] as &$mySource ) { if ( $mySource['ID'] == $content['DefaultSourceID'] ) - $content['SOURCES'][ $mySource['ID'] ]['selected'] = "selected"; + $mySource['selected'] = "selected"; else - $content['SOURCES'][ $mySource['ID'] ]['selected'] = ""; + $mySource['selected'] = ""; } // --- @@ -305,7 +333,7 @@ if ( $content['ENABLEUSEROPTIONS'] ) // --- Init for DefaultSource field! // copy Sources Array $content['USER_SOURCES'] = $content['Sources']; - $userSourceID = GetConfigSetting('DefaultViewsID', $content['DefaultViewsID'], CFGLEVEL_USER); + $userSourceID = GetConfigSetting('DefaultSourceID', $content['DefaultSourceID'], CFGLEVEL_USER); foreach ( $content['USER_SOURCES'] as &$mySource ) { if ( $mySource['ID'] == $userSourceID ) diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index 115e7ad..210c36d 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -78,6 +78,12 @@ $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; $content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; +$content['LN_GEN_OPTIONNAME'] = "Option name"; +$content['LN_GEN_GLOBALVALUE'] = "Global value"; +$content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; +$content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; +$content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; + // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 4822fa0..210c36d 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -78,11 +78,11 @@ $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; $content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; - $content['LN_GEN_OPTIONNAME'] = "Option name"; - $content['LN_GEN_GLOBALVALUE'] = "Global value"; - $content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; - $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; - $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; +$content['LN_GEN_OPTIONNAME'] = "Option name"; +$content['LN_GEN_GLOBALVALUE'] = "Global value"; +$content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; +$content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; +$content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; // User Center diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index 115e7ad..c04ceb3 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -48,6 +48,11 @@ $content['LN_GEN_GLOBAL'] = "Global"; $content['LN_GEN_USERONLY_LONG'] = "For me only
(Only available to your user)"; $content['LN_GEN_GROUPONLY_LONG'] = "For this group
(Only available to the selected group)"; $content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; +$content['LN_GEN_OPTIONNAME'] = "Option name"; +$content['LN_GEN_GLOBALVALUE'] = "Global value"; +$content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; +$content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; +$content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; // General Options diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 34807c2..8820b6b 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -35,9 +35,9 @@
{LN_GEN_WEBSTYLE} {LN_GEN_SELLANGUAGE}
@@ -16,5 +17,7 @@
+

+ \ No newline at end of file From 22200e05ed772d24b000da5f426084c6a1e4841c Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 31 Jul 2008 17:30:11 +0200 Subject: [PATCH 044/142] Added better support to check availibility of logstream sources, and better detailed error descriptions. Also enhanced the error display within all admin pages, again ;)! --- src/admin/sources.php | 68 +++++- src/classes/logstream.class.php | 7 + src/classes/logstreamconfigdisk.class.php | 2 +- src/classes/logstreamdb.class.php | 58 ++++- src/classes/logstreamdisk.class.php | 35 ++- src/classes/logstreampdo.class.php | 102 ++++++--- src/include/constants_errors.php | 3 +- src/include/functions_common.php | 56 ++++- src/include/functions_config.php | 265 +++++++++++----------- src/index.php | 177 +++++++-------- src/lang/de/main.php | 6 +- src/lang/en/main.php | 23 +- src/lang/pt_BR/main.php | 6 +- src/templates/admin/admin_groups.html | 12 +- src/templates/admin/admin_searches.html | 12 +- src/templates/admin/admin_sources.html | 12 +- src/templates/admin/admin_users.html | 12 +- src/templates/admin/admin_views.html | 12 +- src/templates/admin/result.html | 9 +- 19 files changed, 567 insertions(+), 310 deletions(-) diff --git a/src/admin/sources.php b/src/admin/sources.php index e95d6de..080bd13 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -281,9 +281,8 @@ if ( isset($_POST['op']) ) if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } if ( isset($_POST['SourceDBEnableRowCounting']) ) { $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceDBEnableRowCounting']); } // Extra Check for this property - if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) - $_SESSION['SourceDBEnableRowCounting'] = "false"; - + if ( $content['SourceDBEnableRowCounting'] != "true" ) + $content['SourceDBEnableRowCounting'] = "false"; } } @@ -337,26 +336,27 @@ if ( isset($_POST['op']) ) else { // Get plain filename for testing! - $szFileName = DB_StripSlahes($content['SourceDiskFile']); + $content['SourceDiskFileTesting'] = DB_StripSlahes($content['SourceDiskFile']); // Take as it is if rootpath! if ( - ( ($pos = strpos($szFileName, "/")) !== FALSE && $pos == 0) || - ( ($pos = strpos($szFileName, ":\\")) !== FALSE ) || - ( ($pos = strpos($szFileName, ":/")) !== FALSE ) + ( ($pos = strpos($content['SourceDiskFileTesting'], "/")) !== FALSE && $pos == 0) || + ( ($pos = strpos($content['SourceDiskFileTesting'], ":\\")) !== FALSE ) || + ( ($pos = strpos($content['SourceDiskFileTesting'], ":/")) !== FALSE ) ) { // Nothing really todo - $szFileName = $szFileName; + true; } else // prepend basepath! - $szFileName = $gl_root_path . $szFileName; - - if ( !is_file($szFileName) ) + $content['SourceDiskFileTesting'] = $gl_root_path . $content['SourceDiskFileTesting']; +/* + if ( !is_file($content['SourceDiskFileTesting']) ) { $content['ISERROR'] = true; $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $szFileName ); } +*/ } } // DB Params @@ -398,6 +398,52 @@ if ( isset($_POST['op']) ) $content['ISERROR'] = true; $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'], $content['SourceDBType'] ); } + + // --- Verify the Source and report and error if needed! + + // Include LogStream facility + include($gl_root_path . 'classes/logstream.class.php'); + + // First create a tmp source array + $tmpSource['ID'] = $content['SOURCEID']; + $tmpSource['Name'] = $content['Name']; + $tmpSource['SourceType']= $content['SourceType']; + $tmpSource['ViewID'] = $content['SourceViewID']; + if ( $tmpSource['SourceType'] == SOURCE_DISK ) + { + $tmpSource['LogLineType'] = $content['SourceLogLineType']; + $tmpSource['DiskFile'] = $content['SourceDiskFileTesting']; // use SourceDiskFileTesting rather then SourceDiskFile as it is corrected + } + // DB Params + else if ( $tmpSource['SourceType'] == SOURCE_DB || $tmpSource['SourceType'] == SOURCE_PDO ) + { + $tmpSource['DBType'] = $content['SourceDBType']; + $tmpSource['DBName'] = $content['SourceDBName']; + $tmpSource['DBTableType'] = $content['SourceDBTableType']; + $tmpSource['DBServer'] = $content['SourceDBServer']; + $tmpSource['DBTableName'] = $content['SourceDBTableName']; + $tmpSource['DBUser'] = $content['SourceDBUser']; + $tmpSource['DBPassword'] = $content['SourceDBPassword']; + $tmpSource['DBEnableRowCounting'] = $content['SourceDBEnableRowCounting']; + $tmpSource['userid'] = $content['userid']; + $tmpSource['groupid'] = $content['groupid']; + } + + // Init the source + InitSource($tmpSource); + + // Create LogStream Object + $stream = $tmpSource['ObjRef']->LogStreamFactory($tmpSource['ObjRef']); + $res = $stream->Verify(); + if ( $res != SUCCESS ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_WITHINSOURCE'], $tmpSource['Name'], GetErrorMessage($res) ); + + if ( isset($extraErrorDescription) ) + $content['ERROR_MSG'] .= "

" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); + } + // --- } // --- Now ADD/EDIT do the processing! diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index b308764..2513ce9 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -68,6 +68,13 @@ abstract class LogStream { */ public abstract function Close(); + /** + * Verifies the logstream source + * + * @return integer Error stat + */ + public abstract function Verify(); + /** * Read the next data from the current stream. If it reads * forwards or backwards depends on the current read direction. diff --git a/src/classes/logstreamconfigdisk.class.php b/src/classes/logstreamconfigdisk.class.php index 00698b0..8f957e6 100644 --- a/src/classes/logstreamconfigdisk.class.php +++ b/src/classes/logstreamconfigdisk.class.php @@ -63,7 +63,7 @@ class LogStreamConfigDisk extends LogStreamConfig { require_once($gl_root_path . 'classes/logstreamlineparser.class.php'); // Probe if file exists then include it! - $strIncludeFile = 'classes/logstreamlineparser' . $this->LineParserType . '.class.php'; + $strIncludeFile = $gl_root_path . 'classes/logstreamlineparser' . $this->LineParserType . '.class.php'; $strClassName = "LogStreamLineParser" . $this->LineParserType; if ( is_file($strIncludeFile) ) diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index af1375c..3122609 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -84,15 +84,11 @@ class LogStreamDB extends LogStream { { global $dbmapping; - // Try to connect to the database - $this->_dbhandle = mysql_connect($this->_logStreamConfigObj->DBServer,$this->_logStreamConfigObj->DBUser,$this->_logStreamConfigObj->DBPassword); - if (!$this->_dbhandle) - return ERROR_DB_CONNECTFAILED; + // Verify database connection (This also opens the database!) + $res = $this->Verify(); + if ( $res != SUCCESS ) + return $res; - $bRet = mysql_select_db($this->_logStreamConfigObj->DBName, $this->_dbhandle); - if(!$bRet) - return ERROR_DB_CANNOTSELECTDB; - // Copy the Property Array $this->_arrProperties = $arrProperties; @@ -121,6 +117,52 @@ class LogStreamDB extends LogStream { return SUCCESS; } + /** + * Verify if the database connection exists! + * + * @return integer Error state + */ + public function Verify() { + // Try to connect to the database + if ( $this->_dbhandle == null ) + { + $this->_dbhandle = @mysql_connect($this->_logStreamConfigObj->DBServer,$this->_logStreamConfigObj->DBUser,$this->_logStreamConfigObj->DBPassword); + if (!$this->_dbhandle) + { + if ( isset($php_errormsg) ) + { + global $extraErrorDescription; + $extraErrorDescription = $php_errormsg; + } + + // Return error code + return ERROR_DB_CONNECTFAILED; + } + } + + // Select the database now! + $bRet = @mysql_select_db($this->_logStreamConfigObj->DBName, $this->_dbhandle); + if(!$bRet) + { + if ( isset($php_errormsg) ) + { + global $extraErrorDescription; + $extraErrorDescription = $php_errormsg; + } + + // Return error code + return ERROR_DB_CANNOTSELECTDB; + } + + // Check if the table exists! + $numTables = @mysql_num_rows( mysql_query("SHOW TABLES LIKE '%" . $this->_logStreamConfigObj->DBTableName . "%'")); + if( $numTables <= 0 ) + return ERROR_DB_TABLENOTFOUND; + + // reached this point means success ;)! + return SUCCESS; + } + /** * Read the data from a specific uID which means in this * case beginning with from the Database ID diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 7f4ee03..f5ba74a 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -74,18 +74,14 @@ class LogStreamDisk extends LogStream { * @return integer Error stat */ public function Open($arrProperties) { + // Check if file exists! - if(!file_exists($this->_logStreamConfigObj->FileName)) { - return ERROR_FILE_NOT_FOUND; - } - - // Check if file is readable! - if(!is_readable($this->_logStreamConfigObj->FileName)) { - return ERROR_FILE_NOT_READABLE; - } + $result = $this->Verify(); + if ( $result != SUCCESS) + return $result; + // Now open the file $this->_fp = fopen($this->_logStreamConfigObj->FileName, 'r'); - $this->_currentOffset = ftell($this->_fp); $this->_currentStartPos = $this->_currentOffset; $this->_arrProperties = $arrProperties; @@ -111,6 +107,27 @@ class LogStreamDisk extends LogStream { return SUCCESS; } + /** + * Verify if the file exists! + * + * @return integer Error state + */ + public function Verify() { + // Check if file exists! + if(!file_exists($this->_logStreamConfigObj->FileName)) { + return ERROR_FILE_NOT_FOUND; + } + + // Check if file is readable! + if(!is_readable($this->_logStreamConfigObj->FileName)) { + return ERROR_FILE_NOT_READABLE; + } + + // reached this point means success ;)! + return SUCCESS; + } + + private function ReadNextBlock() { $this->_bEOS = false; $bCheckForLastLf = false; diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 5354cb6..4ed509c 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -87,38 +87,11 @@ class LogStreamPDO extends LogStream { { global $dbmapping; - // Create DSN String - $myDBDriver = $this->_logStreamConfigObj->GetPDODatabaseType(); - $myDsn = $this->_logStreamConfigObj->CreateConnectDSN(); - if ( strlen($myDsn) > 0 ) - { - // Check if configured driver is actually loaded! - //print_r(PDO::getAvailableDrivers()); - if ( !in_array($myDBDriver, PDO::getAvailableDrivers()) ) - { - $this->PrintDebugError('PDO Database Driver not loaded: ' . $myDBDriver . "
Please check your php configuration extensions"); - return ERROR_DB_INVALIDDBDRIVER; - } + // Verify database driver and connection (This also opens the database!) + $res = $this->Verify(); + if ( $res != SUCCESS ) + return $res; - try - { - // Try to connect to the database - $this->_dbhandle = new PDO( $myDsn, $this->_logStreamConfigObj->DBUser, $this->_logStreamConfigObj->DBPassword); - -//$handle->setAttribute(PDO::ATTR_TIMEOUT, 3); - } - catch (PDOException $e) - { - $this->PrintDebugError('PDO Database Connection failed: ' . $e->getMessage() . "
DSN: " . $myDsn); - return ERROR_DB_CONNECTFAILED; - } - } - else - { - // Invalid DB Driver! - return ERROR_DB_INVALIDDBDRIVER; - } - // Copy the Property Array $this->_arrProperties = $arrProperties; @@ -155,6 +128,73 @@ class LogStreamPDO extends LogStream { return true; } + /** + * Verify if the database connection exists! + * + * @return integer Error state + */ + public function Verify() { + // Create DSN String + $myDBDriver = $this->_logStreamConfigObj->GetPDODatabaseType(); + $myDsn = $this->_logStreamConfigObj->CreateConnectDSN(); + if ( strlen($myDsn) > 0 ) + { + // Check if configured driver is actually loaded! + //print_r(PDO::getAvailableDrivers()); + if ( !in_array($myDBDriver, PDO::getAvailableDrivers()) ) + { + global $extraErrorDescription; + $extraErrorDescription = "PDO Database Driver not loaded: " . $myDBDriver . "
Please check your php configuration extensions"; + // $this->PrintDebugError($extraErrorDescription); + + // return error code + return ERROR_DB_INVALIDDBDRIVER; + } + + try + { + // Try to connect to the database + $this->_dbhandle = new PDO( $myDsn, $this->_logStreamConfigObj->DBUser, $this->_logStreamConfigObj->DBPassword /*, array(PDO::ATTR_TIMEOUT =>25)*/); + //$this->_dbhandle->setAttribute(PDO::ATTR_TIMEOUT, 25); + } + catch (PDOException $e) + { + global $extraErrorDescription; + $extraErrorDescription = "PDO Database Connection failed: " . $e->getMessage() . "
DSN: " . $myDsn; + // $this->PrintDebugError($extraErrorDescription); + + // return error code + return ERROR_DB_CONNECTFAILED; + } + + try + { + // This is one way to check if the table exists! But I don't really like it tbh -.- + $tmpStmnt = $this->_dbhandle->prepare("SELECT ID FROM " . $this->_logStreamConfigObj->DBTableName . " WHERE ID=1"); + $tmpStmnt->execute(); + $colcount = $tmpStmnt->columnCount(); + if ( $colcount <= 0 ) + return ERROR_DB_TABLENOTFOUND; + } + catch (PDOException $e) + { + global $extraErrorDescription; + $extraErrorDescription = "Could not find table: " . $e->getMessage(); + + // return error code + return ERROR_DB_TABLENOTFOUND; + } + } + else + { + // Invalid DB Driver! + return ERROR_DB_INVALIDDBDRIVER; + } + + // reached this point means success ;)! + return SUCCESS; + } + /** * Read the data from a specific uID which means in this * case beginning with from the Database ID diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index 6ca7855..f3e1361 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -45,6 +45,7 @@ define('ERROR_FILE_NOT_FOUND', 2); define('ERROR_FILE_CANT_CLOSE', 3); define('ERROR_FILE_EOF', 4); define('ERROR_FILE_BOF', 5); +define('ERROR_FILE_NOT_READABLE', 15); define('ERROR_UNDEFINED', 6); define('ERROR_EOS', 7); define('ERROR_NOMORERECORDS', 8); @@ -56,8 +57,8 @@ define('ERROR_DB_QUERYFAILED', 12); define('ERROR_DB_NOPROPERTIES', 13); define('ERROR_DB_INVALIDDBMAPPING', 14); define('ERROR_DB_INVALIDDBDRIVER', 16); +define('ERROR_DB_TABLENOTFOUND', 17); -define('ERROR_FILE_NOT_READABLE', 15); ?> diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 85b5cc9..936fbcb 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -53,8 +53,12 @@ include($gl_root_path . 'include/functions_config.php'); $RUNMODE = RUNMODE_WEBSERVER; $DEBUGMODE = DEBUG_INFO; -// --- Disable ARGV setting @webserver! -ini_set( "register_argc_argv", "Off" ); +// --- Change some runtime variables +// Disable ARGV setting @webserver! +@ini_set( "register_argc_argv", "Off" ); + +// Enable error tracking +@ini_set( "track_errors", "On" ); // --- // Default language @@ -1133,4 +1137,52 @@ function GetConfigSetting($szSettingName, $szDefaultValue = "", $DesiredConfigLe return $szDefaultValue; } +/* +* Helper function to get the errorCode +*/ +function GetErrorMessage($errorCode) +{ + global $content; + + switch( $errorCode ) + { + case ERROR_FILE_NOT_FOUND: + return $content['LN_ERROR_FILE_NOT_FOUND']; + case ERROR_FILE_NOT_READABLE: + return $content['LN_ERROR_FILE_NOT_READABLE']; + case ERROR_FILE_EOF: + return $content['LN_ERROR_FILE_EOF']; + case ERROR_FILE_BOF: + return $content['LN_ERROR_FILE_BOF']; + case ERROR_FILE_CANT_CLOSE: + return $content['LN_ERROR_FILE_CANT_CLOSE']; + case ERROR_UNDEFINED: + return $content['LN_ERROR_UNDEFINED']; + case ERROR_EOS: + return $content['LN_ERROR_EOS']; + case ERROR_NOMORERECORDS: + return $content['LN_ERROR_NORECORDS']; + case ERROR_FILTER_NOT_MATCH: + return $content['LN_ERROR_FILTER_NOT_MATCH']; + case ERROR_DB_CONNECTFAILED: + return $content['LN_ERROR_DB_CONNECTFAILED']; + case ERROR_DB_CANNOTSELECTDB: + return $content['LN_ERROR_DB_CANNOTSELECTDB']; + case ERROR_DB_QUERYFAILED: + return $content['LN_ERROR_DB_QUERYFAILED']; + case ERROR_DB_NOPROPERTIES: + return $content['LN_ERROR_DB_NOPROPERTIES']; + case ERROR_DB_INVALIDDBMAPPING: + return $content['LN_ERROR_DB_INVALIDDBMAPPING']; + case ERROR_DB_INVALIDDBDRIVER: + return $content['LN_ERROR_DB_INVALIDDBDRIVER']; + case ERROR_DB_TABLENOTFOUND: + return $content['LN_ERROR_DB_TABLENOTFOUND']; + + + default: + return GetAndReplaceLangStr( $content['LN_ERROR_UNKNOWN'], $errorCode ); + } +} + ?> \ No newline at end of file diff --git a/src/include/functions_config.php b/src/include/functions_config.php index d884c12..a6d71d5 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -41,141 +41,152 @@ if ( !defined('IN_PHPLOGCON') ) require_once($gl_root_path . 'classes/logstreamconfig.class.php'); // --- +function InitSource(&$mysource) +{ + global $CFG, $content, $gl_root_path, $currentSourceID; + + if ( isset($mysource['SourceType']) ) + { + // Set Array Index, TODO: Check for invalid characters! + $iSourceID = $mysource['ID']; + + // --- Set defaults if not set! + if ( !isset($mysource['LogLineType']) ) + { + $CFG['Sources'][$iSourceID]['LogLineType'] = "syslog"; + $content['Sources'][$iSourceID]['LogLineType'] = "syslog"; + } + + if ( !isset($mysource['userid']) ) + { + $CFG['Sources'][$iSourceID]['userid'] = null; + $content['Sources'][$iSourceID]['userid'] = null; + } + if ( !isset($mysource['groupid']) ) + { + $CFG['Sources'][$iSourceID]['groupid'] = null; + $content['Sources'][$iSourceID]['groupid'] = null; + } + // --- + + // Set default view id to source + $tmpVar = GetConfigSetting("DefaultViewsID", "", CFGLEVEL_USER); + $szDefaultViewID = strlen($tmpVar) > 0 ? $tmpVar : "SYSLOG"; + + if ( isset($_SESSION[$iSourceID . "-View"]) ) + { + // check if view is valid + $UserSessionViewID = $_SESSION[$iSourceID . "-View"]; + + if ( isset($content['Views'][$UserSessionViewID]) ) + { + // Overwrite configured view! + $content['Sources'][$iSourceID]['ViewID'] = $_SESSION[$iSourceID . "-View"]; + } + else + $content['Sources'][$iSourceID]['ViewID'] = $szDefaultViewID; + } + else + { + if ( isset($mysource['ViewID']) && strlen($mysource['ViewID']) > 0 && isset($content['Views'][ $mysource['ViewID'] ]) ) + // Set to configured Source ViewID + $content['Sources'][$iSourceID]['ViewID'] = $mysource['ViewID']; + else + // Not configured, maybe old legacy cfg. Set default view. + $content['Sources'][$iSourceID]['ViewID'] = $szDefaultViewID; + } + + // Only for the display box + $content['Sources'][$iSourceID]['selected'] = ""; + + // Create Config instance! + if ( $mysource['SourceType'] == SOURCE_DISK ) + { + // Perform necessary include + require_once($gl_root_path . 'classes/logstreamconfigdisk.class.php'); + $mysource['ObjRef'] = new LogStreamConfigDisk(); + $mysource['ObjRef']->FileName = $mysource['DiskFile']; + $mysource['ObjRef']->LineParserType = $mysource['LogLineType']; + } + else if ( $mysource['SourceType'] == SOURCE_DB ) + { + // Perform necessary include + require_once($gl_root_path . 'classes/logstreamconfigdb.class.php'); + + $mysource['ObjRef'] = new LogStreamConfigDB(); + $mysource['ObjRef']->DBServer = $mysource['DBServer']; + $mysource['ObjRef']->DBName = $mysource['DBName']; + // Workaround a little bug from the installer script + if ( isset($mysource['DBType']) ) + $mysource['ObjRef']->DBType = $mysource['DBType']; + else + $mysource['ObjRef']->DBType = DB_MYSQL; + + $mysource['ObjRef']->DBTableName = $mysource['DBTableName']; + + // Legacy handling for tabletype! + if ( isset($mysource['DBTableType']) && strtolower($mysource['DBTableType']) == "winsyslog" ) + $mysource['ObjRef']->DBTableType = "monitorware"; // Convert to MonitorWare! + else + $mysource['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); + + // Optional parameters! + if ( isset($mysource['DBPort']) ) { $mysource['ObjRef']->DBPort = $mysource['DBPort']; } + if ( isset($mysource['DBUser']) ) { $mysource['ObjRef']->DBUser = $mysource['DBUser']; } + if ( isset($mysource['DBPassword']) ) { $mysource['ObjRef']->DBPassword = $mysource['DBPassword']; } + if ( isset($mysource['DBEnableRowCounting']) ) { $mysource['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } + } + else if ( $mysource['SourceType'] == SOURCE_PDO ) + { + // Perform necessary include + require_once($gl_root_path . 'classes/logstreamconfigpdo.class.php'); + + $mysource['ObjRef'] = new LogStreamConfigPDO(); + $mysource['ObjRef']->DBServer = $mysource['DBServer']; + $mysource['ObjRef']->DBName = $mysource['DBName']; + $mysource['ObjRef']->DBType = $mysource['DBType']; + $mysource['ObjRef']->DBTableName = $mysource['DBTableName']; + $mysource['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); + + // Optional parameters! + if ( isset($mysource['DBPort']) ) { $mysource['ObjRef']->DBPort = $mysource['DBPort']; } + if ( isset($mysource['DBUser']) ) { $mysource['ObjRef']->DBUser = $mysource['DBUser']; } + if ( isset($mysource['DBPassword']) ) { $mysource['ObjRef']->DBPassword = $mysource['DBPassword']; } + if ( isset($mysource['DBEnableRowCounting']) ) { $mysource['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } + } + else + { + // UNKNOWN, remove config entry! + unset($content['Sources'][$iSourceID]); + + // Output CRITICAL WARNING + DieWithFriendlyErrorMsg( GetAndReplaceLangStr($content['LN_GEN_CRITERROR_UNKNOWNTYPE'], $mysource['SourceType']) ); + } + + // Set generic configuration options + $mysource['ObjRef']->_pageCount = GetConfigSetting("ViewEntriesPerPage", 50); + + // Set default SourceID here! + if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) + $currentSourceID = $iSourceID; + + // Copy Object REF into CFG and content Array as well! + $content['Sources'][$iSourceID]['ObjRef'] = $mysource['ObjRef']; + $CFG['Sources'][$iSourceID]['ObjRef'] = $mysource['ObjRef']; + } +} + function InitSourceConfigs() { - global $CFG, $content, $currentSourceID, $gl_root_path; + global $CFG, $content, $currentSourceID; // Init Source Configs! if ( isset($CFG['Sources']) ) { - $iCount = count($CFG['Sources']); foreach( $CFG['Sources'] as &$mysource ) { - if ( isset($mysource['SourceType']) ) - { - // Set Array Index, TODO: Check for invalid characters! - $iSourceID = $mysource['ID']; - - // --- Set defaults if not set! - if ( !isset($mysource['LogLineType']) ) - { - $CFG['Sources'][$iSourceID]['LogLineType'] = "syslog"; - $content['Sources'][$iSourceID]['LogLineType'] = "syslog"; - } - - if ( !isset($mysource['userid']) ) - { - $CFG['Sources'][$iSourceID]['userid'] = null; - $content['Sources'][$iSourceID]['userid'] = null; - } - if ( !isset($mysource['groupid']) ) - { - $CFG['Sources'][$iSourceID]['groupid'] = null; - $content['Sources'][$iSourceID]['groupid'] = null; - } - // --- - - // Set default view id to source - $tmpVar = GetConfigSetting("DefaultViewsID", "", CFGLEVEL_USER); - $szDefaultViewID = strlen($tmpVar) > 0 ? $tmpVar : "SYSLOG"; - - if ( isset($_SESSION[$iSourceID . "-View"]) ) - { - // check if view is valid - $UserSessionViewID = $_SESSION[$iSourceID . "-View"]; - - if ( isset($content['Views'][$UserSessionViewID]) ) - { - // Overwrite configured view! - $content['Sources'][$iSourceID]['ViewID'] = $_SESSION[$iSourceID . "-View"]; - } - else - $content['Sources'][$iSourceID]['ViewID'] = $szDefaultViewID; - } - else - { - if ( isset($mysource['ViewID']) && strlen($mysource['ViewID']) > 0 && isset($content['Views'][ $mysource['ViewID'] ]) ) - // Set to configured Source ViewID - $content['Sources'][$iSourceID]['ViewID'] = $mysource['ViewID']; - else - // Not configured, maybe old legacy cfg. Set default view. - $content['Sources'][$iSourceID]['ViewID'] = $szDefaultViewID; - } - - // Only for the display box - $content['Sources'][$iSourceID]['selected'] = ""; - - // Create Config instance! - if ( $mysource['SourceType'] == SOURCE_DISK ) - { - // Perform necessary include - require_once($gl_root_path . 'classes/logstreamconfigdisk.class.php'); - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDisk(); - $content['Sources'][$iSourceID]['ObjRef']->FileName = $mysource['DiskFile']; - $content['Sources'][$iSourceID]['ObjRef']->LineParserType = $mysource['LogLineType']; - } - else if ( $mysource['SourceType'] == SOURCE_DB ) - { - // Perform necessary include - require_once($gl_root_path . 'classes/logstreamconfigdb.class.php'); - - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigDB(); - $content['Sources'][$iSourceID]['ObjRef']->DBServer = $mysource['DBServer']; - $content['Sources'][$iSourceID]['ObjRef']->DBName = $mysource['DBName']; - // Workaround a little bug from the installer script - if ( isset($mysource['DBType']) ) - $content['Sources'][$iSourceID]['ObjRef']->DBType = $mysource['DBType']; - else - $content['Sources'][$iSourceID]['ObjRef']->DBType = DB_MYSQL; - - $content['Sources'][$iSourceID]['ObjRef']->DBTableName = $mysource['DBTableName']; - - // Legacy handling for tabletype! - if ( isset($mysource['DBTableType']) && strtolower($mysource['DBTableType']) == "winsyslog" ) - $content['Sources'][$iSourceID]['ObjRef']->DBTableType = "monitorware"; // Convert to MonitorWare! - else - $content['Sources'][$iSourceID]['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); - - // Optional parameters! - if ( isset($mysource['DBPort']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPort = $mysource['DBPort']; } - if ( isset($mysource['DBUser']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBUser = $mysource['DBUser']; } - if ( isset($mysource['DBPassword']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPassword = $mysource['DBPassword']; } - if ( isset($mysource['DBEnableRowCounting']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } - } - else if ( $mysource['SourceType'] == SOURCE_PDO ) - { - // Perform necessary include - require_once($gl_root_path . 'classes/logstreamconfigpdo.class.php'); - - $content['Sources'][$iSourceID]['ObjRef'] = new LogStreamConfigPDO(); - $content['Sources'][$iSourceID]['ObjRef']->DBServer = $mysource['DBServer']; - $content['Sources'][$iSourceID]['ObjRef']->DBName = $mysource['DBName']; - $content['Sources'][$iSourceID]['ObjRef']->DBType = $mysource['DBType']; - $content['Sources'][$iSourceID]['ObjRef']->DBTableName = $mysource['DBTableName']; - $content['Sources'][$iSourceID]['ObjRef']->DBTableType = strtolower($mysource['DBTableType']); - - // Optional parameters! - if ( isset($mysource['DBPort']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPort = $mysource['DBPort']; } - if ( isset($mysource['DBUser']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBUser = $mysource['DBUser']; } - if ( isset($mysource['DBPassword']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBPassword = $mysource['DBPassword']; } - if ( isset($mysource['DBEnableRowCounting']) ) { $content['Sources'][$iSourceID]['ObjRef']->DBEnableRowCounting = $mysource['DBEnableRowCounting']; } - } - else - { - // UNKNOWN, remove config entry! - unset($content['Sources'][$iSourceID]); - - // Output CRITICAL WARNING - DieWithFriendlyErrorMsg( GetAndReplaceLangStr($content['LN_GEN_CRITERROR_UNKNOWNTYPE'], $mysource['SourceType']) ); - } - - // Set generic configuration options - $content['Sources'][$iSourceID]['ObjRef']->_pageCount = GetConfigSetting("ViewEntriesPerPage", 50); - - // Set default SourceID here! - if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) - $currentSourceID = $iSourceID; - } + // Init each source using this function! + InitSource($mysource); } } diff --git a/src/index.php b/src/index.php index 8ab2091..c7aea25 100644 --- a/src/index.php +++ b/src/index.php @@ -104,89 +104,6 @@ $content['searchstr'] = ""; $content['highlightstr'] = ""; $content['EXPAND_HIGHLIGHT'] = "false"; -// --- BEGIN Define Helper functions -function HighLightString($highlightArray, $strmsg) -{ - if ( isset($highlightArray) ) - { - // TODO OPTIMIZE - USING FONT TAG as SPAN is HIDDEN if MESSAGE POPUP is ENABNLED! - foreach( $highlightArray as $highlightword ) - $strmsg = preg_replace( "/(" . $highlightword['highlight'] . ")/i", '\\1', $strmsg ); - } - - // return result - return $strmsg; -} - -function PrepareStringForSearch($myString) -{ - return str_replace(" ", "+", $myString); -} -// --- - -// --- Read and process filters from search dialog! -if ( (isset($_POST['search']) || isset($_GET['search'])) || (isset($_POST['filter']) || isset($_GET['filter'])) ) -{ - // Copy search over - if ( isset($_POST['search']) ) - $mysearch = $_POST['search']; - else if ( isset($_GET['search']) ) - $mysearch = $_GET['search']; - - if ( isset($_POST['filter']) ) - $myfilter = $_POST['filter']; - else if ( isset($_GET['filter']) ) - $myfilter = $_GET['filter']; - - // Optionally read highlight words - if ( isset($_POST['highlight']) ) - $content['highlightstr'] = $_POST['highlight']; - else if ( isset($_GET['highlight']) ) - $content['highlightstr'] = $_GET['highlight']; - -// else if ( $mysearch == $content['LN_SEARCH']) - { - // Message is just appended - if ( isset($myfilter) && strlen($myfilter) > 0 ) - $content['searchstr'] = $myfilter; - } - - if ( strlen($content['highlightstr']) > 0 ) - { - $searchArray = array("\\", "/", ".", ">"); - $replaceArray = array("\\\\", "\/", "\.", ">"); - - // user also wants to highlight words! - if ( strpos($content['highlightstr'], ",") === false) - { - - $content['highlightwords'][0]['highlight_raw'] = $content['highlightstr']; - $content['highlightwords'][0]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightstr']); - $content['highlightwords'][0]['cssclass'] = "highlight_1"; - $content['highlightwords'][0]['htmlcode'] = '' . $content['highlightwords'][0]['highlight']. ''; - } - else - { - // Split array into words - $tmparray = explode( ",", $content['highlightstr'] ); - foreach( $tmparray as $word ) - $content['highlightwords'][]['highlight_raw'] = $word; - - // Assign other variables needed for this array entry - for ($i = 0; $i < count($content['highlightwords']); $i++) - { - $content['highlightwords'][$i]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightwords'][$i]['highlight_raw']); - $content['highlightwords'][$i]['cssclass'] = "highlight_" . ($i+1); - $content['highlightwords'][$i]['htmlcode'] = '' . $content['highlightwords'][$i]['highlight']. ''; - } - } - - // Default expand Highlight Arrea! - $content['EXPAND_HIGHLIGHT'] = "true"; - } -} -// --- - // --- BEGIN CREATE TITLE $content['TITLE'] = InitPageTitle(); @@ -272,7 +189,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c { // This will disable to Main SyslogView and show an error message $content['syslogmessagesenabled'] = "false"; - $content['detailederror'] = "No syslog messages found."; + $content['detailederror'] = $content['LN_ERROR_NORECORDS']; } // --- @@ -713,13 +630,10 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c { // This will disable to Main SyslogView and show an error message $content['syslogmessagesenabled'] = "false"; + $content['detailederror'] = GetErrorMessage($res); - if ( $res == ERROR_FILE_NOT_FOUND ) - $content['detailederror'] = "Syslog file could not be found."; - else if ( $res == ERROR_FILE_NOT_READABLE ) - $content['detailederror'] = "Syslog file is not readable, read access may be denied. "; - else - $content['detailederror'] = "Unknown or unhandled error occured (Error Code " . $res . ") "; + if ( isset($extraErrorDescription) ) + $content['detailederror'] .= "

" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); } // Close file! @@ -733,4 +647,87 @@ $page -> parser($content, "index.html"); $page -> output(); // --- +// --- BEGIN Define Helper functions +function HighLightString($highlightArray, $strmsg) +{ + if ( isset($highlightArray) ) + { + // TODO OPTIMIZE - USING FONT TAG as SPAN is HIDDEN if MESSAGE POPUP is ENABNLED! + foreach( $highlightArray as $highlightword ) + $strmsg = preg_replace( "/(" . $highlightword['highlight'] . ")/i", '\\1', $strmsg ); + } + + // return result + return $strmsg; +} + +function PrepareStringForSearch($myString) +{ + return str_replace(" ", "+", $myString); +} +// --- + +// --- Read and process filters from search dialog! +if ( (isset($_POST['search']) || isset($_GET['search'])) || (isset($_POST['filter']) || isset($_GET['filter'])) ) +{ + // Copy search over + if ( isset($_POST['search']) ) + $mysearch = $_POST['search']; + else if ( isset($_GET['search']) ) + $mysearch = $_GET['search']; + + if ( isset($_POST['filter']) ) + $myfilter = $_POST['filter']; + else if ( isset($_GET['filter']) ) + $myfilter = $_GET['filter']; + + // Optionally read highlight words + if ( isset($_POST['highlight']) ) + $content['highlightstr'] = $_POST['highlight']; + else if ( isset($_GET['highlight']) ) + $content['highlightstr'] = $_GET['highlight']; + +// else if ( $mysearch == $content['LN_SEARCH']) + { + // Message is just appended + if ( isset($myfilter) && strlen($myfilter) > 0 ) + $content['searchstr'] = $myfilter; + } + + if ( strlen($content['highlightstr']) > 0 ) + { + $searchArray = array("\\", "/", ".", ">"); + $replaceArray = array("\\\\", "\/", "\.", ">"); + + // user also wants to highlight words! + if ( strpos($content['highlightstr'], ",") === false) + { + + $content['highlightwords'][0]['highlight_raw'] = $content['highlightstr']; + $content['highlightwords'][0]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightstr']); + $content['highlightwords'][0]['cssclass'] = "highlight_1"; + $content['highlightwords'][0]['htmlcode'] = '' . $content['highlightwords'][0]['highlight']. ''; + } + else + { + // Split array into words + $tmparray = explode( ",", $content['highlightstr'] ); + foreach( $tmparray as $word ) + $content['highlightwords'][]['highlight_raw'] = $word; + + // Assign other variables needed for this array entry + for ($i = 0; $i < count($content['highlightwords']); $i++) + { + $content['highlightwords'][$i]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightwords'][$i]['highlight_raw']); + $content['highlightwords'][$i]['cssclass'] = "highlight_" . ($i+1); + $content['highlightwords'][$i]['htmlcode'] = '' . $content['highlightwords'][$i]['highlight']. ''; + } + } + + // Default expand Highlight Arrea! + $content['EXPAND_HIGHLIGHT'] = "true"; + } +} +// --- + ?> \ No newline at end of file diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 1844500..4a56a87 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -60,6 +60,10 @@ $content['LN_GEN_SOURCE_DB'] = "Datenbank"; $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; + $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error: '%2'"; +$content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden."; + + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; $content['LN_MENU_SHOWEVENTS'] = "Show Events"; @@ -102,8 +106,6 @@ $content['LN_AUTORELOAD_PRECONFIGURED'] = "Preconfigured auto reload "; $content['LN_AUTORELOAD_SECONDS'] = "seconds"; $content['LN_AUTORELOAD_MINUTES'] = "minutes"; -$content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden."; - // Filter Options $content['LN_FILTER_DATE'] = "Zeitliche Abgrenzung"; $content['LN_FILTER_DATEMODE'] = "Zeitraum auswählen"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index a330118..c4873bf 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -61,6 +61,27 @@ $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; + $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error:
%2"; + $content['LN_SOURCES_ERROR_EXTRAMSG'] = "Extra Error Details:
%1"; +$content['LN_ERROR_NORECORDS'] = "No syslog records found"; +$content['LN_ERROR_FILE_NOT_FOUND'] = "Syslog file could not be found"; +$content['LN_ERROR_FILE_NOT_READABLE'] = "Syslog file is not readable, read access may be denied"; +$content['LN_ERROR_UNKNOWN'] = "Unknown or unhandled error occured (Error Code '%1')"; +$content['LN_ERROR_FILE_EOF'] = "End of File reached"; +$content['LN_ERROR_FILE_BOF'] = "Begin of File reeached"; +$content['LN_ERROR_FILE_CANT_CLOSE'] = "Can't close File"; +$content['LN_ERROR_UNDEFINED'] = "Undefined Error"; +$content['LN_ERROR_EOS'] = "End of stream reached"; +$content['LN_ERROR_FILTER_NOT_MATCH'] = "Filter does not match any results"; +$content['LN_ERROR_DB_CONNECTFAILED'] = "Connection to the database server failed"; +$content['LN_ERROR_DB_CANNOTSELECTDB'] = "Could not find the configured database"; +$content['LN_ERROR_DB_QUERYFAILED'] = "Dataquery failed to execute"; +$content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; +$content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; +$content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; +$content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; +$content['LN_ERROR_'] = ""; + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; $content['LN_MENU_SHOWEVENTS'] = "Show Events"; @@ -102,8 +123,6 @@ $content['LN_AUTORELOAD_PRECONFIGURED'] = "Preconfigured auto reload "; $content['LN_AUTORELOAD_SECONDS'] = "seconds"; $content['LN_AUTORELOAD_MINUTES'] = "minutes"; -$content['LN_ERROR_NORECORDS'] = "No syslog records found."; - // Filter Options $content['LN_FILTER_DATE'] = "Datetime Range"; $content['LN_FILTER_DATEMODE'] = "Select mode"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index f4e46e9..9980e20 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -64,6 +64,10 @@ $content['LN_GEN_SELECTVIEW'] = "Visão"; $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; + $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error: '%2'"; +$content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; + + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; $content['LN_MENU_SHOWEVENTS'] = "Show Events"; @@ -105,8 +109,6 @@ $content['LN_AUTORELOAD_PRECONFIGURED'] = "Auto recarregamento pré-config $content['LN_AUTORELOAD_SECONDS'] = "segundos"; $content['LN_AUTORELOAD_MINUTES'] = "minutos"; -$content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; - // Filter Options $content['LN_FILTER_DATE'] = "Intervalo Data/Hora"; $content['LN_FILTER_DATEMODE'] = "Selecionar Modo"; diff --git a/src/templates/admin/admin_groups.html b/src/templates/admin/admin_groups.html index 39fb83f..9b9af7b 100644 --- a/src/templates/admin/admin_groups.html +++ b/src/templates/admin/admin_groups.html @@ -1,12 +1,16 @@ -
+

-

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

-{LN_GEN_ERRORRETURNPREV} +
+
{LN_GEN_ERRORDETAILS}
+

{ERROR_MSG}

+
+

+ {LN_GEN_ERRORRETURNPREV}
-
+

diff --git a/src/templates/admin/admin_searches.html b/src/templates/admin/admin_searches.html index 8154bae..5476310 100644 --- a/src/templates/admin/admin_searches.html +++ b/src/templates/admin/admin_searches.html @@ -1,12 +1,16 @@ -
+

-

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

-{LN_GEN_ERRORRETURNPREV} +
+
{LN_GEN_ERRORDETAILS}
+

{ERROR_MSG}

+
+

+ {LN_GEN_ERRORRETURNPREV}
-
+

diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index 3a33998..ad835af 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -30,12 +30,16 @@ -
+

-

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

-{LN_GEN_ERRORRETURNPREV} +
+
{LN_GEN_ERRORDETAILS}
+

{ERROR_MSG}

+
+

+ {LN_GEN_ERRORRETURNPREV}
-
+

diff --git a/src/templates/admin/admin_users.html b/src/templates/admin/admin_users.html index 71e897f..4e2a3f0 100644 --- a/src/templates/admin/admin_users.html +++ b/src/templates/admin/admin_users.html @@ -1,12 +1,16 @@ -
+

-

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

-{LN_GEN_ERRORRETURNPREV} +
+
{LN_GEN_ERRORDETAILS}
+

{ERROR_MSG}

+
+

+ {LN_GEN_ERRORRETURNPREV}
-
+

diff --git a/src/templates/admin/admin_views.html b/src/templates/admin/admin_views.html index d302e8a..3c0ba98 100644 --- a/src/templates/admin/admin_views.html +++ b/src/templates/admin/admin_views.html @@ -1,12 +1,16 @@ -
+

-

{LN_GEN_ERRORDETAILS} {ERROR_MSG}

-{LN_GEN_ERRORRETURNPREV} +
+
{LN_GEN_ERRORDETAILS}
+

{ERROR_MSG}

+
+

+ {LN_GEN_ERRORRETURNPREV}
-
+

diff --git a/src/templates/admin/result.html b/src/templates/admin/result.html index 7238dac..de4f0dc 100644 --- a/src/templates/admin/result.html +++ b/src/templates/admin/result.html @@ -1,7 +1,7 @@

-
+
@@ -9,10 +9,11 @@ From eaf9b75e4514064368e56e9fecee7ddaf4636e88 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 31 Jul 2008 17:35:40 +0200 Subject: [PATCH 045/142] Fixed spelling error in admin index template --- src/templates/admin/admin_index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 8820b6b..77a0151 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -208,7 +208,7 @@ - + From 833b0c46e658ac0581bdfac797e0f1b39937fd01 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 31 Jul 2008 17:46:23 +0200 Subject: [PATCH 046/142] Added missing strings into other languages --- src/lang/de/main.php | 22 ++++++++++++++++++---- src/lang/en/main.php | 40 ++++++++++++++++++---------------------- src/lang/pt_BR/main.php | 21 ++++++++++++++++++--- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 4a56a87..bfbed70 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -61,8 +61,24 @@ $content['LN_GEN_SOURCE_DB'] = "Datenbank"; $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error: '%2'"; + $content['LN_SOURCES_ERROR_EXTRAMSG'] = "Extra Error Details:
%1"; $content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden."; - + $content['LN_ERROR_FILE_NOT_FOUND'] = "Syslog file could not be found"; + $content['LN_ERROR_FILE_NOT_READABLE'] = "Syslog file is not readable, read access may be denied"; + $content['LN_ERROR_UNKNOWN'] = "Unknown or unhandled error occured (Error Code '%1')"; + $content['LN_ERROR_FILE_EOF'] = "End of File reached"; + $content['LN_ERROR_FILE_BOF'] = "Begin of File reeached"; + $content['LN_ERROR_FILE_CANT_CLOSE'] = "Can't close File"; + $content['LN_ERROR_UNDEFINED'] = "Undefined Error"; + $content['LN_ERROR_EOS'] = "End of stream reached"; + $content['LN_ERROR_FILTER_NOT_MATCH'] = "Filter does not match any results"; + $content['LN_ERROR_DB_CONNECTFAILED'] = "Connection to the database server failed"; + $content['LN_ERROR_DB_CANNOTSELECTDB'] = "Could not find the configured database"; + $content['LN_ERROR_DB_QUERYFAILED'] = "Dataquery failed to execute"; + $content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; + $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; + $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; + $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; @@ -250,7 +266,5 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; - $content['LN_CONVERT_'] = ""; - $content['LN_CONVERT_'] = ""; - $content['LN_CONVERT_'] = ""; + ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index c4873bf..5ff580f 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -56,13 +56,13 @@ $content['LN_GEN_DB_DB2'] = " IBM DB2"; $content['LN_GEN_DB_FIREBIRD'] = "Firebird/Interbase 6"; $content['LN_GEN_DB_INFORMIX'] = "IBM Informix Dynamic Server"; $content['LN_GEN_DB_SQLITE'] = "SQLite 2"; - $content['LN_GEN_SELECTVIEW'] = "Select View"; - $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; - $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; - $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; +$content['LN_GEN_SELECTVIEW'] = "Select View"; +$content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; +$content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; +$content['LN_GEN_ERRORDETAILS'] = "Error Details:"; - $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error:
%2"; - $content['LN_SOURCES_ERROR_EXTRAMSG'] = "Extra Error Details:
%1"; +$content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error:
%2"; +$content['LN_SOURCES_ERROR_EXTRAMSG'] = "Extra Error Details:
%1"; $content['LN_ERROR_NORECORDS'] = "No syslog records found"; $content['LN_ERROR_FILE_NOT_FOUND'] = "Syslog file could not be found"; $content['LN_ERROR_FILE_NOT_READABLE'] = "Syslog file is not readable, read access may be denied"; @@ -80,19 +80,18 @@ $content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; -$content['LN_ERROR_'] = ""; // Topmenu Entries - $content['LN_MENU_SEARCH'] = "Search"; - $content['LN_MENU_SHOWEVENTS'] = "Show Events"; - $content['LN_MENU_HELP'] = "Help"; - $content['LN_MENU_SEARCHINKB'] = "Search in Knowledge Base"; - $content['LN_MENU_LOGIN'] = "Login"; - $content['LN_MENU_ADMINCENTER'] = "Admin Center"; - $content['LN_MENU_LOGOFF'] = "Logoff"; - $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; - $content['LN_MENU_MAXVIEW'] = "Maximize View"; - $content['LN_MENU_NORMALVIEW'] = "Normalize View"; +$content['LN_MENU_SEARCH'] = "Search"; +$content['LN_MENU_SHOWEVENTS'] = "Show Events"; +$content['LN_MENU_HELP'] = "Help"; +$content['LN_MENU_SEARCHINKB'] = "Search in Knowledge Base"; +$content['LN_MENU_LOGIN'] = "Login"; +$content['LN_MENU_ADMINCENTER'] = "Admin Center"; +$content['LN_MENU_LOGOFF'] = "Logoff"; +$content['LN_MENU_LOGGEDINAS'] = "Logged in as"; +$content['LN_MENU_MAXVIEW'] = "Maximize View"; +$content['LN_MENU_NORMALVIEW'] = "Normalize View"; // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Warning! You still have NOT removed the 'install.php' from your phpLogCon main directory!"; @@ -110,8 +109,8 @@ $content['LN_VIEW_MESSAGECENTERED'] = "Back to unfiltered view with this message $content['LN_VIEW_RELATEDMSG'] = "View related syslog messages"; $content['LN_VIEW_FILTERFOR'] = "Filter message for "; $content['LN_VIEW_SEARCHFOR'] = "Search online for "; - $content['LN_VIEW_SEARCHFORGOOGLE'] = "Search Google for "; - $content['LN_GEN_MESSAGEDETAILS'] = "Message Details"; +$content['LN_VIEW_SEARCHFORGOOGLE'] = "Search Google for "; +$content['LN_GEN_MESSAGEDETAILS'] = "Message Details"; $content['LN_HIGHLIGHT'] = "Hightlight >>"; $content['LN_HIGHLIGHT_OFF'] = "Hightlight <<"; @@ -268,9 +267,6 @@ $content['LN_CONVERT_STEP6'] = "Step 8 - Done"; $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; -$content['LN_CONVERT_'] = ""; -$content['LN_CONVERT_'] = ""; -$content['LN_CONVERT_'] = ""; ?> \ No newline at end of file diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 9980e20..4f66f2d 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -65,7 +65,24 @@ $content['LN_GEN_SELECTVIEW'] = "Visão"; $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error: '%2'"; + $content['LN_SOURCES_ERROR_EXTRAMSG'] = "Extra Error Details:
%1"; $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; + $content['LN_ERROR_FILE_NOT_FOUND'] = "Syslog file could not be found"; + $content['LN_ERROR_FILE_NOT_READABLE'] = "Syslog file is not readable, read access may be denied"; + $content['LN_ERROR_UNKNOWN'] = "Unknown or unhandled error occured (Error Code '%1')"; + $content['LN_ERROR_FILE_EOF'] = "End of File reached"; + $content['LN_ERROR_FILE_BOF'] = "Begin of File reeached"; + $content['LN_ERROR_FILE_CANT_CLOSE'] = "Can't close File"; + $content['LN_ERROR_UNDEFINED'] = "Undefined Error"; + $content['LN_ERROR_EOS'] = "End of stream reached"; + $content['LN_ERROR_FILTER_NOT_MATCH'] = "Filter does not match any results"; + $content['LN_ERROR_DB_CONNECTFAILED'] = "Connection to the database server failed"; + $content['LN_ERROR_DB_CANNOTSELECTDB'] = "Could not find the configured database"; + $content['LN_ERROR_DB_QUERYFAILED'] = "Dataquery failed to execute"; + $content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; + $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; + $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; + $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; // Topmenu Entries @@ -253,7 +270,5 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully converted your existing phpLogCon installation :)!

Important! Don\'t forget to REMOVE THE VARIABLES $CFG[\'UserDBConvertAllowed\'] = true; from your config.php file!

You can click here to get to your phpLogConinstallation.'; $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; - $content['LN_CONVERT_'] = ""; - $content['LN_CONVERT_'] = ""; - $content['LN_CONVERT_'] = ""; + ?> \ No newline at end of file From 3d275e65fd8af852c18322e6f9cdd40e09b62ae9 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 1 Aug 2008 10:56:49 +0200 Subject: [PATCH 047/142] Fixed a bug in the installer when the UserDB System was not enabled during installation. --- src/install.php | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/install.php b/src/install.php index 85bdc62..ffaffb4 100644 --- a/src/install.php +++ b/src/install.php @@ -257,7 +257,7 @@ else if ( $content['INSTALL_STEP'] == 3 ) if ( isset($_GET['errormsg']) ) { $content['iserror'] = "true"; - $content['errormsg'] = DB_RemoveBadChars( urldecode($_GET['errormsg']) ); + $content['errormsg'] = urldecode($_GET['errormsg']); } } else if ( $content['INSTALL_STEP'] == 4 ) @@ -449,7 +449,7 @@ else if ( $content['INSTALL_STEP'] == 6 ) if ( isset($_GET['errormsg']) ) { $content['iserror'] = "true"; - $content['errormsg'] = DB_RemoveBadChars( urldecode($_GET['errormsg']) ); + $content['errormsg'] = urldecode($_GET['errormsg']); } } else // NO Database means NO user management, so next step! @@ -541,7 +541,7 @@ else if ( $content['INSTALL_STEP'] == 7 ) if ( isset($_GET['errormsg']) ) { $content['iserror'] = "true"; - $content['errormsg'] = DB_RemoveBadChars( urldecode($_GET['errormsg']) ); + $content['errormsg'] = urldecode($_GET['errormsg']); } } else if ( $content['INSTALL_STEP'] == 8 ) @@ -624,14 +624,23 @@ else if ( $content['INSTALL_STEP'] == 8 ) if ( $_SESSION['SourceDBEnableRowCounting'] != "true" ) $_SESSION['SourceDBEnableRowCounting'] = "false"; } + + // Check Database Access! + } // If we reached this point, we have gathered all necessary information to create our configuration file ;)! $filebuffer = LoadDataFile($configsamplefile); - // helper variables - if ( $_SESSION['UserDBEnabled'] ) { $_SESSION['UserDBEnabled_value'] = "true"; } else { $_SESSION['UserDBEnabled_value'] = "false"; } - if ( $_SESSION['UserDBLoginRequired'] ) { $_SESSION['UserDBLoginRequired_value'] = "true"; } else { $_SESSION['UserDBLoginRequired_value'] = "false"; } + // Sez helper variables and init user vars if needed! + if ( isset($_SESSION['UserDBEnabled']) && $_SESSION['UserDBEnabled'] ) { $_SESSION['UserDBEnabled_value'] = "true"; } else { $_SESSION['UserDBEnabled_value'] = "false"; } + if ( isset($_SESSION['UserDBLoginRequired']) && $_SESSION['UserDBLoginRequired'] ) { $_SESSION['UserDBLoginRequired_value'] = "true"; } else { $_SESSION['UserDBLoginRequired_value'] = "false"; } + if ( !isset($_SESSION['UserDBServer'])) { $_SESSION['UserDBServer'] = "localhost"; } + if ( !isset($_SESSION['UserDBPort'])) { $_SESSION['UserDBPort'] = "3306"; } + if ( !isset($_SESSION['UserDBName'])) { $_SESSION['UserDBName'] = "phplogcon"; } + if ( !isset($_SESSION['UserDBPref'])) { $_SESSION['UserDBPref'] = "logcon_"; } + if ( !isset($_SESSION['UserDBUser'])) { $_SESSION['UserDBUser'] = "root"; } + if ( !isset($_SESSION['UserDBPass'])) { $_SESSION['UserDBPass'] = ""; } // Start replacing existing sample configurations $patterns[] = "/\\\$CFG\['ViewMessageCharacterLimit'\] = [0-9]{1,2};/"; @@ -646,6 +655,7 @@ else if ( $content['INSTALL_STEP'] == 8 ) $patterns[] = "/\\\$CFG\['UserDBUser'\] = (.*?);/"; $patterns[] = "/\\\$CFG\['UserDBPass'\] = (.*?);/"; $patterns[] = "/\\\$CFG\['UserDBLoginRequired'\] = (.*?);/"; + $replacements[] = "\$CFG['ViewMessageCharacterLimit'] = " . $_SESSION['ViewMessageCharacterLimit'] . ";"; $replacements[] = "\$CFG['ViewEntriesPerPage'] = " . $_SESSION['ViewEntriesPerPage'] . ";"; $replacements[] = "\$CFG['ViewEnableDetailPopups'] = " . $_SESSION['ViewEnableDetailPopups'] . ";"; @@ -660,7 +670,7 @@ else if ( $content['INSTALL_STEP'] == 8 ) $replacements[] = "\$CFG['UserDBLoginRequired'] = " . $_SESSION['UserDBLoginRequired_value'] . ";"; //User Database Options - if ( $_SESSION['UserDBEnabled'] == 1 ) + if ( isset($_SESSION['UserDBEnabled']) && $_SESSION['UserDBEnabled'] ) { // TODO! } From 2b1a313cc4677577598b1d82e8c8ad5dd175be61 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 1 Aug 2008 12:16:12 +0200 Subject: [PATCH 048/142] Incremented build and added changelog entry --- ChangeLog | 12 +++++++++++- src/include/functions_common.php | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 585b3d7..0a39d4f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,15 @@ --------------------------------------------------------------------------- -Version 2.5.1 (beta), 2008-07-29 +Version 2.5.2 (devel), 2008-08-01 +- General Options are now configureable on user basis, if UserDB System + is installed of course. This means first, phpLogCon used the global + configured options, then the user configured - if available. +- When you add new Sources, the source is checked with better error details. + If you have a database connection, even the existence of the configured + table is checked. This helps locating configuration problems. +- Fixed minor bug in the installer which was added in v2.5.0. +- Fixed spelling errors in the admin index template. +--------------------------------------------------------------------------- +Version 2.5.1 (devel), 2008-07-29 - Added a new option to suppress displaying multiple messages. This means if you have two or more messages of the exact same text one after another, only ONE message will be shown. This helps to "compress" the diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 936fbcb..f4c227a 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -66,7 +66,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.5.1"; +$content['BUILDNUMBER'] = "2.5.2"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! From 55b447262db0c4299cecac1c77f084c6a74a4171 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 1 Aug 2008 12:18:00 +0200 Subject: [PATCH 049/142] Added something to the changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 0a39d4f..f16b6a1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,7 @@ Version 2.5.2 (devel), 2008-08-01 table is checked. This helps locating configuration problems. - Fixed minor bug in the installer which was added in v2.5.0. - Fixed spelling errors in the admin index template. +- Error messages in the admin center contain more useful details now. --------------------------------------------------------------------------- Version 2.5.1 (devel), 2008-07-29 - Added a new option to suppress displaying multiple messages. This From 47124f1c81263aad704866ae24167e518ba0bf5d Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 1 Aug 2008 17:10:56 +0200 Subject: [PATCH 050/142] Fixed a cut & paste error in the index.php which caused the filter not to work. This change was added when I was reordering the helper functions to the lower end of the file. --- src/index.php | 126 +++++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/src/index.php b/src/index.php index c7aea25..ba0f4a4 100644 --- a/src/index.php +++ b/src/index.php @@ -114,6 +114,69 @@ else $content['TITLE'] .= " :: All Syslogmessages"; // --- END CREATE TITLE +// --- Read and process filters from search dialog! +if ( (isset($_POST['search']) || isset($_GET['search'])) || (isset($_POST['filter']) || isset($_GET['filter'])) ) +{ + // Copy search over + if ( isset($_POST['search']) ) + $mysearch = $_POST['search']; + else if ( isset($_GET['search']) ) + $mysearch = $_GET['search']; + + if ( isset($_POST['filter']) ) + $myfilter = $_POST['filter']; + else if ( isset($_GET['filter']) ) + $myfilter = $_GET['filter']; + + // Optionally read highlight words + if ( isset($_POST['highlight']) ) + $content['highlightstr'] = $_POST['highlight']; + else if ( isset($_GET['highlight']) ) + $content['highlightstr'] = $_GET['highlight']; + +// else if ( $mysearch == $content['LN_SEARCH']) + { + // Message is just appended + if ( isset($myfilter) && strlen($myfilter) > 0 ) + $content['searchstr'] = $myfilter; + } + + if ( strlen($content['highlightstr']) > 0 ) + { + $searchArray = array("\\", "/", ".", ">"); + $replaceArray = array("\\\\", "\/", "\.", ">"); + + // user also wants to highlight words! + if ( strpos($content['highlightstr'], ",") === false) + { + + $content['highlightwords'][0]['highlight_raw'] = $content['highlightstr']; + $content['highlightwords'][0]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightstr']); + $content['highlightwords'][0]['cssclass'] = "highlight_1"; + $content['highlightwords'][0]['htmlcode'] = '' . $content['highlightwords'][0]['highlight']. ''; + } + else + { + // Split array into words + $tmparray = explode( ",", $content['highlightstr'] ); + foreach( $tmparray as $word ) + $content['highlightwords'][]['highlight_raw'] = $word; + + // Assign other variables needed for this array entry + for ($i = 0; $i < count($content['highlightwords']); $i++) + { + $content['highlightwords'][$i]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightwords'][$i]['highlight_raw']); + $content['highlightwords'][$i]['cssclass'] = "highlight_" . ($i+1); + $content['highlightwords'][$i]['htmlcode'] = '' . $content['highlightwords'][$i]['highlight']. ''; + } + } + + // Default expand Highlight Arrea! + $content['EXPAND_HIGHLIGHT'] = "true"; + } +} +// --- + // --- BEGIN Custom Code if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$currentSourceID]['SourceType'] == SOURCE_DISK ) { @@ -667,67 +730,4 @@ function PrepareStringForSearch($myString) } // --- -// --- Read and process filters from search dialog! -if ( (isset($_POST['search']) || isset($_GET['search'])) || (isset($_POST['filter']) || isset($_GET['filter'])) ) -{ - // Copy search over - if ( isset($_POST['search']) ) - $mysearch = $_POST['search']; - else if ( isset($_GET['search']) ) - $mysearch = $_GET['search']; - - if ( isset($_POST['filter']) ) - $myfilter = $_POST['filter']; - else if ( isset($_GET['filter']) ) - $myfilter = $_GET['filter']; - - // Optionally read highlight words - if ( isset($_POST['highlight']) ) - $content['highlightstr'] = $_POST['highlight']; - else if ( isset($_GET['highlight']) ) - $content['highlightstr'] = $_GET['highlight']; - -// else if ( $mysearch == $content['LN_SEARCH']) - { - // Message is just appended - if ( isset($myfilter) && strlen($myfilter) > 0 ) - $content['searchstr'] = $myfilter; - } - - if ( strlen($content['highlightstr']) > 0 ) - { - $searchArray = array("\\", "/", ".", ">"); - $replaceArray = array("\\\\", "\/", "\.", ">"); - - // user also wants to highlight words! - if ( strpos($content['highlightstr'], ",") === false) - { - - $content['highlightwords'][0]['highlight_raw'] = $content['highlightstr']; - $content['highlightwords'][0]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightstr']); - $content['highlightwords'][0]['cssclass'] = "highlight_1"; - $content['highlightwords'][0]['htmlcode'] = '' . $content['highlightwords'][0]['highlight']. ''; - } - else - { - // Split array into words - $tmparray = explode( ",", $content['highlightstr'] ); - foreach( $tmparray as $word ) - $content['highlightwords'][]['highlight_raw'] = $word; - - // Assign other variables needed for this array entry - for ($i = 0; $i < count($content['highlightwords']); $i++) - { - $content['highlightwords'][$i]['highlight'] = str_replace( $searchArray, $replaceArray, $content['highlightwords'][$i]['highlight_raw']); - $content['highlightwords'][$i]['cssclass'] = "highlight_" . ($i+1); - $content['highlightwords'][$i]['htmlcode'] = '' . $content['highlightwords'][$i]['highlight']. ''; - } - } - - // Default expand Highlight Arrea! - $content['EXPAND_HIGHLIGHT'] = "true"; - } -} -// --- - ?> \ No newline at end of file From f57b612e339f2fa8baf431a9e892edb1a028ba1f Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 4 Aug 2008 15:19:32 +0200 Subject: [PATCH 051/142] Prepared phpLogCon to support export formats --- src/images/icons/export1.png | Bin 0 -> 857 bytes src/include/constants_general.php | 8 ++++ src/include/functions_common.php | 61 +++++++++++++++++++++++++++++- src/index.php | 2 + src/lang/en/main.php | 8 +++- src/templates/index.html | 32 +++++++++++++--- 6 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/images/icons/export1.png diff --git a/src/images/icons/export1.png b/src/images/icons/export1.png new file mode 100644 index 0000000000000000000000000000000000000000..113a7cda23e2fc6e0f604e028b01cb508d113528 GIT binary patch literal 857 zcmV-f1E&0mP)WdKBJATc-~MrC3kGB7YRATcvKF)}(eI3O!9F)%RlwXN9z000McNliru z)&&<0G6A15LT~^8010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00OZ| zL_t(|+J%$LPZLoT#?MSU?a)HUB5|xu!3UK}0+QlN4Z*}mjBGH$m2ssDUC5t7NQfJ@ z#Emf;WZ{O=q@*B}NMi?#Vz4buX=-VqP}>e2I@8B^#l3}KoPN?G&T%k5jiSJ?ojj}W=BR`pl&GOH&Ob{#o=;RZ*I3JXP`1!EW#UBC>+}%69ysa;^ zDIF{Db!vAu?A?zx_mg+oQw*qqfde4{9Tqc0^9gGvAcP9La=5aT-=VHPOiWBL$z*Ei zbQ}A~1uvj1mw*BqUcYyNQDR{~&C5_E&*IO`Sbl)yYXZ~L)9rghErN8-ukcjgT zI2nN+YZFMSBY1mfL2lA%RlZ)|6PL?%adL9t_fiC20FOHFbpJ&Iyl__j0 zLqquL7$q$DJ~ubVp_?{bO1s5kAr==G6-P41aVrjoV>OjZMJv`<5am#nh`M8-EawDr zj%C@KqobqGmzI`_9*<|?5CiiY$R{LRe=q6 EXPORT_CVS, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_CVS'] ); + $content['EXPORTTYPES'][EXPORT_HTML] = array( "ID" => EXPORT_HTML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_HTML'] ); + $content['EXPORTTYPES'][EXPORT_EXCEL] = array( "ID" => EXPORT_EXCEL, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_EXCEL'] ); + + // Add formats by loaded extensions + if ( $content['XML_IS_ENABLED'] ) + $content['EXPORTTYPES'][EXPORT_XML] = array( "ID" => EXPORT_XML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_XML'] ); + if ( $content['PDF_IS_ENABLED'] ) + $content['EXPORTTYPES'][EXPORT_PDF] = array( "ID" => EXPORT_PDF, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_PDF'] ); + +} + function CreatePredefinedSearches() { global $CFG, $content; @@ -367,13 +387,49 @@ function InitPhpDebugMode() function CheckAndSetRunMode() { - global $RUNMODE, $MaxExecutionTime; + global $content, $RUNMODE, $MaxExecutionTime; // Set to command line mode if argv is set! if ( !isset($_SERVER["GATEWAY_INTERFACE"]) ) $RUNMODE = RUNMODE_COMMANDLINE; // Obtain max_execution_time $MaxExecutionTime = ini_get("max_execution_time"); + + // --- Check necessary PHP Extensions! + $loadedExtensions = get_loaded_extensions(); + + // Check for GD libary + if ( in_array("gd", $loadedExtensions) ) + $content['GD_IS_ENABLED'] = true; + else + $content['GD_IS_ENABLED'] = false; + + // Check MYSQL Extension + if ( in_array("mysql", $loadedExtensions) ) + $content['MYSQL_IS_ENABLED'] = true; + else + $content['MYSQL_IS_ENABLED'] = false; + + // Check PDO Extension + if ( in_array("PDO", $loadedExtensions) ) + $content['PDO_IS_ENABLED'] = true; + else + $content['PDO_IS_ENABLED'] = false; + // --- + + // Check XML Extension + if ( in_array("xml", $loadedExtensions) ) + $content['XML_IS_ENABLED'] = true; + else + $content['XML_IS_ENABLED'] = false; + // --- + + // Check PDF Extension + if ( in_array("pdf", $loadedExtensions) ) + $content['PDF_IS_ENABLED'] = true; + else + $content['PDF_IS_ENABLED'] = false; + // --- } function InitRuntimeInformations() @@ -448,7 +504,8 @@ function InitFrontEndVariables() $content['MENU_MAXIMIZE'] = $content['BASEPATH'] . "images/icons/table_selection_all.png"; $content['MENU_NORMAL'] = $content['BASEPATH'] . "images/icons/table_selection_block.png"; $content['MENU_USEROPTIONS'] = $content['BASEPATH'] . "images/icons/businessman_preferences.png"; - + $content['MENU_EXPORT'] = $content['BASEPATH'] . "images/icons/export1.png"; + $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; $content['MENU_PAGER_NEXT'] = $content['BASEPATH'] . "images/icons/media_fast_forward.png"; diff --git a/src/index.php b/src/index.php index ba0f4a4..0b2c0a3 100644 --- a/src/index.php +++ b/src/index.php @@ -86,6 +86,8 @@ else $content['skipone'] = false; // --- +// Init Export Stuff! +$content['EXPORT_ENABLED'] = true; // Init Pager variables // $content['uid_previous'] = UID_UNKNOWN; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 5ff580f..eeeb968 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -60,7 +60,6 @@ $content['LN_GEN_SELECTVIEW'] = "Select View"; $content['LN_GEN_CRITERROR_UNKNOWNTYPE'] = "The source type '%1' is not supported by phpLogCon yet. This is a critical error, please fix your configuration."; $content['LN_GEN_ERRORRETURNPREV'] = "Click here to return to the previous site."; $content['LN_GEN_ERRORDETAILS'] = "Error Details:"; - $content['LN_SOURCES_ERROR_WITHINSOURCE'] = "The source '%1' checking returned with an error:
%2"; $content['LN_SOURCES_ERROR_EXTRAMSG'] = "Extra Error Details:
%1"; $content['LN_ERROR_NORECORDS'] = "No syslog records found"; @@ -80,6 +79,13 @@ $content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; + $content['LN_GEN_SELECTEXPORT'] = "Select to Export"; + $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; + $content['LN_GEN_EXPORT_HTML'] = "HTML"; + $content['LN_GEN_EXPORT_XML'] = "XML"; + $content['LN_GEN_EXPORT_PDF'] = "PDF"; + $content['LN_GEN_EXPORT_EXCEL'] = "Excel"; + // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/index.html b/src/templates/index.html index c531566..75db873 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -89,12 +89,34 @@
{LN_ADMIN_CENTER}
-

- {SZMSG} -

+

+ {SZMSG} +

You will be redirected to the this page on {REDIRSECONDS} seconds. +

{LN_GEN_ENABLEGZIP}
- + + + +
Recent syslog messagesRecent syslog messages + + + + + + +
+ + + +
+ +
+ + + + + + From c740edd9fe2496d119e61cdd42b18d1b601ec790 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 2 Sep 2008 16:51:50 +0200 Subject: [PATCH 071/142] Minor fix to the new msg parser --- src/classes/msgparsers/msgparser.eventlog.class.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/classes/msgparsers/msgparser.eventlog.class.php b/src/classes/msgparsers/msgparser.eventlog.class.php index cd54942..fea3bb5 100644 --- a/src/classes/msgparsers/msgparser.eventlog.class.php +++ b/src/classes/msgparsers/msgparser.eventlog.class.php @@ -78,8 +78,11 @@ class MsgParser_eventlog extends MsgParser { if ( $this->_MsgNormalize == 1 ) { + //Init tmp msg + $szTmpMsg = ""; + // Create Field Array to prepend into msg! Reverse Order here - $myFields = array( SYSLOG_EVENT_CATEGORY, SYSLOG_EVENT_LOGTYPE, SYSLOG_EVENT_SOURCE, SYSLOG_EVENT_USER, SYSLOG_EVENT_ID ); + $myFields = array( SYSLOG_MESSAGE, SYSLOG_EVENT_CATEGORY, SYSLOG_EVENT_LOGTYPE, SYSLOG_EVENT_SOURCE, SYSLOG_EVENT_USER, SYSLOG_EVENT_ID ); foreach ( $myFields as $myField ) { @@ -90,9 +93,12 @@ class MsgParser_eventlog extends MsgParser { $szFieldName = $fields[$myField]['FieldCaptionID']; // Append Field into msg - $arrArguments[SYSLOG_MESSAGE] = $szFieldName . "='" . $arrArguments[$myField] . "' " . $arrArguments[SYSLOG_MESSAGE]; + $szTmpMsg = $szFieldName . ": '" . $arrArguments[$myField] . "'\n" . $szTmpMsg; } + // copy finished MSG back! + $arrArguments[SYSLOG_MESSAGE] = $szTmpMsg; + } } else From a36c801960d970a5b88fd6e474dc7e0932dc64b2 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 2 Sep 2008 17:26:53 +0200 Subject: [PATCH 072/142] Added support to import/convert new source options --- src/include/functions_installhelpers.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/include/functions_installhelpers.php b/src/include/functions_installhelpers.php index 656483a..e0e5cfb 100644 --- a/src/include/functions_installhelpers.php +++ b/src/include/functions_installhelpers.php @@ -160,9 +160,11 @@ function ConvertCustomSources() // Add New Entry if ( $mySource['SourceType'] == SOURCE_DISK ) { - $result = DB_Query("INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, LogLineType, DiskFile) VALUES ( " . + $result = DB_Query("INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, LogLineType, DiskFile) VALUES ( " . "'" . PrepareValueForDB($mySource['Name']) . "', " . " " . PrepareValueForDB($mySource['SourceType']) . " , " . + "'" . PrepareValueForDB($mySource['MsgParserList']) . "', " . + " " . PrepareValueForDB($mySource['MsgNormalize']) . " , " . "'" . PrepareValueForDB($mySource['ViewID']) . "', " . "'" . PrepareValueForDB($mySource['LogLineType']) . "', " . "'" . PrepareValueForDB($mySource['DiskFile']) . "'" . @@ -179,9 +181,11 @@ function ConvertCustomSources() $mySource['DBType'] = DB_MYSQL; // Perform the insert - $result = DB_Query("INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting) VALUES ( " . + $result = DB_Query("INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting) VALUES ( " . "'" . PrepareValueForDB($mySource['Name']) . "', " . " " . PrepareValueForDB($mySource['SourceType']) . " , " . + "'" . PrepareValueForDB($mySource['MsgParserList']) . "', " . + " " . PrepareValueForDB($mySource['MsgNormalize']) . " , " . "'" . PrepareValueForDB($mySource['ViewID']) . "', " . "'" . PrepareValueForDB($mySource['DBTableType']) . "', " . " " . PrepareValueForDB($mySource['DBType']) . " , " . From 83caf3ad7451ea359308c7f750aeb4d99cee8d87 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 3 Sep 2008 10:53:43 +0200 Subject: [PATCH 073/142] Fixed notice issues in main admin page --- src/admin/index.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index 8a76a2d..d669fc5 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -201,18 +201,18 @@ if ( isset($_POST['op']) ) // Set checkbox States -if ($content['ViewUseTodayYesterday'] == 1) { $content['ViewUseTodayYesterday_checked'] = "checked"; } else { $content['ViewUseTodayYesterday_checked'] = ""; } -if ($content['ViewEnableDetailPopups'] == 1) { $content['ViewEnableDetailPopups_checked'] = "checked"; } else { $content['ViewEnableDetailPopups_checked'] = ""; } -if ($content['EnableIPAddressResolve'] == 1) { $content['EnableIPAddressResolve_checked'] = "checked"; } else { $content['EnableIPAddressResolve_checked'] = ""; } +if (isset($content['ViewUseTodayYesterday']) && $content['ViewUseTodayYesterday'] == 1) { $content['ViewUseTodayYesterday_checked'] = "checked"; } else { $content['ViewUseTodayYesterday_checked'] = ""; } +if (isset($content['ViewEnableDetailPopups']) && $content['ViewEnableDetailPopups'] == 1) { $content['ViewEnableDetailPopups_checked'] = "checked"; } else { $content['ViewEnableDetailPopups_checked'] = ""; } +if (isset($content['EnableIPAddressResolve']) && $content['EnableIPAddressResolve'] == 1) { $content['EnableIPAddressResolve_checked'] = "checked"; } else { $content['EnableIPAddressResolve_checked'] = ""; } -if ($content['MiscShowDebugMsg'] == 1) { $content['MiscShowDebugMsg_checked'] = "checked"; } else { $content['MiscShowDebugMsg_checked'] = ""; } -if ($content['MiscShowDebugGridCounter'] == 1) { $content['MiscShowDebugGridCounter_checked'] = "checked"; } else { $content['MiscShowDebugGridCounter_checked'] = ""; } -if ($content['MiscShowPageRenderStats'] == 1) { $content['MiscShowPageRenderStats_checked'] = "checked"; } else { $content['MiscShowPageRenderStats_checked'] = ""; } -if ($content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompression_checked'] = "checked"; } else { $content['MiscEnableGzipCompression_checked'] = ""; } -if ($content['SuppressDuplicatedMessages'] == 1) { $content['SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['SuppressDuplicatedMessages_checked'] = ""; } +if (isset($content['MiscShowDebugMsg']) && $content['MiscShowDebugMsg'] == 1) { $content['MiscShowDebugMsg_checked'] = "checked"; } else { $content['MiscShowDebugMsg_checked'] = ""; } +if (isset($content['MiscShowDebugGridCounter']) && $content['MiscShowDebugGridCounter'] == 1) { $content['MiscShowDebugGridCounter_checked'] = "checked"; } else { $content['MiscShowDebugGridCounter_checked'] = ""; } +if (isset($content['MiscShowPageRenderStats']) && $content['MiscShowPageRenderStats'] == 1) { $content['MiscShowPageRenderStats_checked'] = "checked"; } else { $content['MiscShowPageRenderStats_checked'] = ""; } +if (isset($content['MiscEnableGzipCompression']) && $content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompression_checked'] = "checked"; } else { $content['MiscEnableGzipCompression_checked'] = ""; } +if (isset($content['SuppressDuplicatedMessages']) && $content['SuppressDuplicatedMessages'] == 1) { $content['SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['SuppressDuplicatedMessages_checked'] = ""; } -if ($content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } -if ($content['MiscDebugToSyslog'] == 1) { $content['MiscDebugToSyslog_checked'] = "checked"; } else { $content['MiscDebugToSyslog_checked'] = ""; } +if (isset($content['DebugUserLogin']) && $content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } +if (isset($content['MiscDebugToSyslog']) && $content['MiscDebugToSyslog'] == 1) { $content['MiscDebugToSyslog_checked'] = "checked"; } else { $content['MiscDebugToSyslog_checked'] = ""; } // --- // --- Init for Style field! From ace4e3c84f99c9f0ab030bb56d4a91eac7c58463 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 3 Sep 2008 11:32:16 +0200 Subject: [PATCH 074/142] Fixed a bug that files network were not correctly checked in the logstream disk source --- src/admin/sources.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/admin/sources.php b/src/admin/sources.php index a75bc05..43d7231 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -352,6 +352,7 @@ if ( isset($_POST['op']) ) // Take as it is if rootpath! if ( ( ($pos = strpos($content['SourceDiskFileTesting'], "/")) !== FALSE && $pos == 0) || + ( ($pos = strpos($content['SourceDiskFileTesting'], "\\\\")) !== FALSE && $pos == 0) || ( ($pos = strpos($content['SourceDiskFileTesting'], ":\\")) !== FALSE ) || ( ($pos = strpos($content['SourceDiskFileTesting'], ":/")) !== FALSE ) ) From 00456c2f0d6e4df9254a70004eee9d6bea353435 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 3 Sep 2008 13:41:16 +0200 Subject: [PATCH 075/142] Added changelog entry --- ChangeLog | 13 +++++++++++++ src/include/functions_common.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 65abd39..42404cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,17 @@ --------------------------------------------------------------------------- +Version 2.5.6 (devel), 2008-09-03 +- Implemented Message Parser facility. This new extendable facility of + phpLogCon helps splitting messages into fields. And the fields can + be filtered and searched for, this expands the full potential of + phpLogCon. A message parser for windows eventlog logfiles generated + by Adiscon products is included. +- Linebreaks within messages are now displayed in the popup window, + and the detail page. +- Added Database Upgrade functionalety, the reason is simple, because + there was an update to the database structure. If you are using the + User Management system, you will be prompted to upgrade your database + next time you login. +--------------------------------------------------------------------------- Version 2.5.5 (devel), 2008-08-28 - Added option to send debug messages (warnings and error's) from phpLogCon to the local syslog server on linux. On Windows, the debug diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 2e13104..7750d07 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -66,7 +66,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.5.5"; +$content['BUILDNUMBER'] = "2.5.6"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! From 7c0c12ca487ded92c3cca45623bda5c019e2aeff Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 3 Sep 2008 16:20:43 +0200 Subject: [PATCH 076/142] Started implementing charts generator and statistic page --- src/chartgenerator.php | 230 ++++++++++++++++++++++++++++++ src/details.php | 7 +- src/include/constants_general.php | 6 + src/include/functions_common.php | 1 + src/lang/de/main.php | 1 + src/lang/en/main.php | 5 + src/lang/pt_BR/main.php | 1 + src/statistics.php | 109 ++++++++++++++ src/templates/include_menu.html | 5 +- src/templates/statistics.html | 37 +++++ 10 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 src/chartgenerator.php create mode 100644 src/statistics.php create mode 100644 src/templates/statistics.html diff --git a/src/chartgenerator.php b/src/chartgenerator.php new file mode 100644 index 0000000..a1dd9b8 --- /dev/null +++ b/src/chartgenerator.php @@ -0,0 +1,230 @@ + This file will create gfx of charts, and handle image caching + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +include($gl_root_path . 'classes/logstream.class.php'); + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! +// --- + +// --- READ CONTENT Vars +if ( isset($_GET['type']) ) + $content['chart_type'] = intval($_GET['type']); +else + $content['chart_type'] = CHART_CAKE; + +if ( isset($_GET['width']) ) + $content['chart_width'] = intval($_GET['width']); +else + $content['chart_width'] = 100; + +if ( isset($_GET['byfield']) ) + $content['chart_field'] = $_GET['byfield']; +else +{ + $content['error_occured'] = true; + $content['error_details'] = $content['LN_GEN_ERROR_MISSINGCHARTFIELD']; +} +// --- + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +// --- END CREATE TITLE + +// --- BEGIN Custom Code +if ( !$content['error_occured'] ) +{ + if ( isset($content['Sources'][$currentSourceID]) ) + { + // Obtain and get the Config Object + $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; + + // Create LogStream Object + $stream = $stream_config->LogStreamFactory($stream_config); + + $res = $stream->Open( $content['Columns'], true ); + if ( $res == SUCCESS ) + { + + + + } + else + { + // This will disable to Main SyslogView and show an error message + $content['error_occured'] = true; + $content['error_details'] = GetErrorMessage($res); + if ( isset($extraErrorDescription) ) + $content['error_details'] .= "

" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); + } + + // Close file! + $stream->Close(); + } + else + { + $content['error_occured'] = true; + $content['error_details'] = GetAndReplaceLangStr( $content['LN_GEN_ERROR_SOURCENOTFOUND'], $currentSourceID); + } +} +// --- + +// --- Convert and Output +if ( $content['error_occured'] ) +{ + // TODO PRINT ERROR ON PICTURE STREAM! + +// InitTemplateParser(); +// $page -> parser($content, "export.html"); +// $page -> output(); +} +else +{ + // Create ChartDiagram! + + exit; + + // Create a CVS File! + $szOutputContent = ""; + $szOutputMimeType = "text/plain"; + $szOutputCharset = ""; + + $szOutputFileName = "ExportMessages"; + $szOutputFileExtension = ".txt"; + if ( $content['exportformat'] == EXPORT_CVS ) + { + // Set MIME TYPE and File Extension + $szOutputMimeType = "text/csv"; + $szOutputFileExtension = ".csv"; + + // Set Column line in cvs file! + foreach($content['Columns'] as $mycolkey) + { + if ( isset($fields[$mycolkey]) ) + { + // Prepend Comma if needed + if (strlen($szOutputContent) > 0) + $szOutputContent .= ","; + + // Append column name + $szOutputContent .= $content[ $fields[$mycolkey]['FieldCaptionID'] ]; + } + } + + // Append line break + $szOutputContent .= "\n"; + + // Append messages into output + foreach ( $content['syslogmessages'] as $myIndex => $mySyslogMessage ) + { + $szLine = ""; + + // --- Process columns + foreach($mySyslogMessage as $myColkey => $mySyslogField) + { + // Prepend Comma if needed + if (strlen($szLine) > 0) + $szLine .= ","; + + // Append field contents + $szLine .= '"' . str_replace('"', '\\"', $mySyslogField['fieldvalue']) . '"'; + } + // --- + + // Append line! + $szOutputContent .= $szLine . "\n"; + } + } + else if ( $content['exportformat'] == EXPORT_XML ) + { + // Set MIME TYPE and File Extension + $szOutputMimeType = "application/xml"; + $szOutputFileExtension = ".xml"; + $szOutputCharset = "charset=UTF-8"; + + // Create XML Header and first node!! + $szOutputContent .= "\xef\xbb\xbf"; + $szOutputContent .= "\n"; + $szOutputContent .= "\n"; + + // Append messages into output + foreach ( $content['syslogmessages'] as $myIndex => $mySyslogMessage ) + { + $szXmlLine = "\t\n"; + + // --- Process columns + foreach($mySyslogMessage as $myColkey => $mySyslogField) + { + +// if ( isset($content[ $fields[$mycolkey]['FieldCaptionID'] ]) ) +// $szNodeTitle = $content[ $fields[$mycolkey]['FieldCaptionID'] ]; +// else + + // Append field content | first run htmlentities,tnen utf8 encoding!! + $szXmlLine .= "\t\t<" . $myColkey . ">" . utf8_encode( htmlentities($mySyslogField['fieldvalue']) ) . "\n"; + } + // --- + + $szXmlLine .= "\t\n"; + + // Append line! + $szOutputContent .= $szXmlLine; + } + + // End first XML Node + $szOutputContent .= ""; + } + + // Set needed Header properties + header('Content-type: ' . $szOutputMimeType . "; " . $szOutputCharset); + header("Content-Length: " . strlen($szOutputContent) ); + header('Content-Disposition: attachment; filename="' . $szOutputFileName . $szOutputFileExtension . '"'); + + // Output Content! + print( $szOutputContent ); +} +// --- + +?> \ No newline at end of file diff --git a/src/details.php b/src/details.php index 737e086..aca7f12 100644 --- a/src/details.php +++ b/src/details.php @@ -348,12 +348,11 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['uid_current' $content['error_code'] = $ret; if ( $ret == ERROR_FILE_NOT_FOUND ) - $content['detailederror'] = "Syslog file could not be found."; + $content['detailederror'] = $content['LN_ERROR_FILE_NOT_FOUND']; else if ( $ret == ERROR_FILE_NOT_READABLE ) - $content['detailederror'] = "Syslog file is not readable, read access may be denied. "; + $content['detailederror'] = $content['LN_ERROR_FILE_NOT_READABLE']; else - $content['detailederror'] = "Unknown or unhandeled error occured."; - + $content['detailederror'] = $content['LN_ERROR_UNKNOWN']; } // Close file! diff --git a/src/include/constants_general.php b/src/include/constants_general.php index 9ce4070..3421475 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -74,6 +74,12 @@ define('EXPORT_CVS', 'CVS'); define('EXPORT_XML', 'XML'); // --- +// --- GFX Chart Types +define('CHART_CAKE', 1); +define('CHART_BARS_VERTICAL', 2); +define('CHART_BARS_HORIZONTAL', 3); +// --- + // --- define('UID_UNKNOWN', -1); // --- diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 7750d07..809654d 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -482,6 +482,7 @@ function InitFrontEndVariables() $content['MENU_NORMAL'] = $content['BASEPATH'] . "images/icons/table_selection_block.png"; $content['MENU_USEROPTIONS'] = $content['BASEPATH'] . "images/icons/businessman_preferences.png"; $content['MENU_EXPORT'] = $content['BASEPATH'] . "images/icons/export1.png"; + $content['MENU_CHARTS'] = $content['BASEPATH'] . "images/icons/line-chart.png"; $content['MENU_PAGER_BEGIN'] = $content['BASEPATH'] . "images/icons/media_beginning.png"; $content['MENU_PAGER_PREVIOUS'] = $content['BASEPATH'] . "images/icons/media_rewind.png"; diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 65925fd..423cb00 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -98,6 +98,7 @@ $content['LN_MENU_LOGOFF'] = "Logoff"; $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; $content['LN_MENU_MAXVIEW'] = "Maximize View"; $content['LN_MENU_NORMALVIEW'] = "Normalize View"; + $content['LN_MENU_STATISTICS'] = "Statistics"; // Index Site diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 5294703..888645b 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -98,6 +98,7 @@ $content['LN_MENU_LOGOFF'] = "Logoff"; $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; $content['LN_MENU_MAXVIEW'] = "Maximize View"; $content['LN_MENU_NORMALVIEW'] = "Normalize View"; + $content['LN_MENU_STATISTICS'] = "Statistics"; // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Warning! You still have NOT removed the 'install.php' from your phpLogCon main directory!"; @@ -278,5 +279,9 @@ $content['LN_CONVERT_STEP6_TEXT'] = 'Congratulations! You have successfully conv $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; +// Stats Site + $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; + $content['LN_STATS_GRAPH'] = "Graph"; + ?> \ No newline at end of file diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 69680c7..4300dac 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -103,6 +103,7 @@ $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; $content['LN_MENU_LOGGEDINAS'] = "Logged in as"; $content['LN_MENU_MAXVIEW'] = "Maximize View"; $content['LN_MENU_NORMALVIEW'] = "Normalize View"; + $content['LN_MENU_STATISTICS'] = "Statistics"; // Main Index Site $content['LN_ERROR_INSTALLFILEREMINDER'] = "Atenção! Você ainda NÃO removeu o arquivo 'install.php' do diretório de seu phpLogCon!"; diff --git a/src/statistics.php b/src/statistics.php new file mode 100644 index 0000000..3de9ebc --- /dev/null +++ b/src/statistics.php @@ -0,0 +1,109 @@ + Shows Statistic, Charts and more + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +include($gl_root_path . 'classes/logstream.class.php'); + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! +// --- + +// --- CONTENT Vars +// --- + +// --- BEGIN Custom Code +/*if ( isset($content['Sources'][$currentSourceID]) ) +{ + // Obtain and get the Config Object + $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; + + // Create LogStream Object + $stream = $stream_config->LogStreamFactory($stream_config); + $res = $stream->Open( $content['AllColumns'], true ); + if ( $res == SUCCESS ) + { + // This will enable to Stats View + $content['statsenabled'] = "true"; + + + + } + else + { + // This will disable to Stats View and show an error message + $content['statsenabled'] = "false"; + + // Set error code + $content['error_code'] = $ret; + + if ( $ret == ERROR_FILE_NOT_FOUND ) + $content['detailederror'] = $content['LN_ERROR_FILE_NOT_FOUND']; + else if ( $ret == ERROR_FILE_NOT_READABLE ) + $content['detailederror'] = $content['LN_ERROR_FILE_NOT_READABLE']; + else + $content['detailederror'] = $content['LN_ERROR_UNKNOWN']; + } + + // Close file! + $stream->Close(); +} +*/ + +// --- + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); + +// Append custom title part! +$content['TITLE'] .= " :: " . $content['LN_MENU_STATISTICS']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "statistics.html"); +$page -> output(); +// --- + + +?> \ No newline at end of file diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index 25cc2dd..409b5bb 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -1,13 +1,14 @@
{LN_GEN_PAGE} {main_currentpagenumber} @@ -109,8 +131,7 @@ + From 74fc838c450ecbe48c57392b56d566e16b5a49a6 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 4 Aug 2008 17:21:43 +0200 Subject: [PATCH 052/142] Added code file which does the export stuff. XML and CSV Format already works. --- src/export.php | 419 +++++++++++++++++++++++++++++++ src/include/functions_common.php | 4 +- src/index.php | 7 +- src/lang/en/main.php | 6 +- src/templates/export.html | 17 ++ src/templates/index.html | 20 +- 6 files changed, 458 insertions(+), 15 deletions(-) create mode 100644 src/export.php create mode 100644 src/templates/export.html diff --git a/src/export.php b/src/export.php new file mode 100644 index 0000000..a7a4f9b --- /dev/null +++ b/src/export.php @@ -0,0 +1,419 @@ + Exports data from a search and site into a data format + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Include LogStream facility +include($gl_root_path . 'classes/logstream.class.php'); + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! +// --- + +// --- READ CONTENT Vars +if ( isset($_GET['uid']) ) + $content['uid_current'] = intval($_GET['uid']); +else + $content['uid_current'] = UID_UNKNOWN; + +// Read direction parameter +if ( isset($_GET['direction']) && $_GET['direction'] == "desc" ) + $content['read_direction'] = EnumReadDirection::Forward; +else + $content['read_direction'] = EnumReadDirection::Backward; + +// If direction is DESC, should we SKIP one? +if ( isset($_GET['skipone']) && $_GET['skipone'] == "true" ) + $content['skipone'] = true; +else + $content['skipone'] = false; + +// Init variables +$content['searchstr'] = ""; +$content['error_occured'] = false; + +// Check required input parameters +if ( + (isset($_GET['op']) && $_GET['op'] == "export") && + (isset($_GET['exporttype']) && array_key_exists($_GET['exporttype'], $content['EXPORTTYPES'])) + ) + $content['exportformat'] = $_GET['exporttype']; +else +{ + $content['error_occured'] = true; + $content['error_details'] = $content['LN_GEN_ERROR_INVALIDEXPORTTYPE']; +} +// --- + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); + +// Append custom title part! +if ( isset($content['searchstr']) && strlen($content['searchstr']) > 0 ) + $content['TITLE'] .= " :: Results for the search '" . $content['searchstr'] . "'"; // Append search +else + $content['TITLE'] .= " :: Syslogmessages"; +// --- END CREATE TITLE + +// --- Read and process filters from search dialog! +if ( (isset($_POST['search']) || isset($_GET['search'])) || (isset($_POST['filter']) || isset($_GET['filter'])) ) +{ + // Copy search over + if ( isset($_POST['search']) ) + $mysearch = $_POST['search']; + else if ( isset($_GET['search']) ) + $mysearch = $_GET['search']; + + if ( isset($_POST['filter']) ) + $myfilter = $_POST['filter']; + else if ( isset($_GET['filter']) ) + $myfilter = $_GET['filter']; + + // Message is just appended + if ( isset($myfilter) && strlen($myfilter) > 0 ) + $content['searchstr'] = $myfilter; +} +// --- + +// --- BEGIN Custom Code +if ( !$content['error_occured'] ) +{ + if ( isset($content['Sources'][$currentSourceID]) ) + { + // Obtain and get the Config Object + $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; + + // Create LogStream Object + $stream = $stream_config->LogStreamFactory($stream_config); + $stream->SetFilter($content['searchstr']); + + // Copy current used columns here! + $content['Columns'] = $content['Views'][$currentViewID]['Columns']; + + // --- Init the fields we need + foreach($content['Columns'] as $mycolkey) + { + if ( isset($fields[$mycolkey]) ) + { + $content['fields'][$mycolkey]['FieldID'] = $mycolkey; + $content['fields'][$mycolkey]['FieldCaption'] = $content[ $fields[$mycolkey]['FieldCaptionID'] ]; + $content['fields'][$mycolkey]['FieldType'] = $fields[$mycolkey]['FieldType']; + $content['fields'][$mycolkey]['DefaultWidth'] = $fields[$mycolkey]['DefaultWidth']; + } + } + // --- + + $res = $stream->Open( $content['Columns'], true ); + if ( $res == SUCCESS ) + { + // TODO Implement ORDER + $stream->SetReadDirection($content['read_direction']); + + // Set current ID and init Counter + $uID = $content['uid_current']; + + $counter = 0; + + // If uID is known, we need to init READ first - this will also seek for available records first! + if ($uID != UID_UNKNOWN) + { + // First read will also set the start position of the Stream! + $ret = $stream->Read($uID, $logArray); + } + else + $ret = $stream->ReadNext($uID, $logArray); + + // --- Check if Read was successfull! + if ( $ret == SUCCESS ) + { + // If Forward direction is used, we need to SKIP one entry! + if ( $content['read_direction'] == EnumReadDirection::Forward ) + { + if ( $content['skipone'] ) + { + // Skip this entry and move to the next + $stream->ReadNext($uID, $logArray); + } + } + } + else + { + // This will disable to Main SyslogView and show an error message + $content['error_occured'] = true; + $content['error_details'] = $content['LN_ERROR_NORECORDS']; + } + // --- + + // We found matching records, so continue + if ( $ret == SUCCESS ) + { + //Loop through the messages! + do + { + // --- Extra stuff for suppressing messages + if ( + GetConfigSetting("SuppressDuplicatedMessages", 0, CFGLEVEL_USER) == 1 + && + isset($logArray[SYSLOG_MESSAGE]) + ) + { + + if ( !isset($szLastMessage) ) // Only set lastmgr + $szLastMessage = $logArray[SYSLOG_MESSAGE]; + else + { + // Skip if same msg + if ( $szLastMessage == $logArray[SYSLOG_MESSAGE] ) + { + // Set last mgr + $szLastMessage = $logArray[SYSLOG_MESSAGE]; + + // Skip entry + continue; + } + } + } + // --- + + // --- Now we populate the values array! + foreach($content['Columns'] as $mycolkey) + { + if ( isset($fields[$mycolkey]) && isset($logArray[$mycolkey]) ) + { + // Set defaults + $content['syslogmessages'][$counter][$mycolkey]['FieldColumn'] = $mycolkey; + $content['syslogmessages'][$counter][$mycolkey]['uid'] = $uID; + + // Copy value as it is first! + $content['syslogmessages'][$counter][$mycolkey]['fieldvalue'] = $logArray[$mycolkey]; + + // Now handle fields types differently + if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_DATE ) + { + $content['syslogmessages'][$counter][$mycolkey]['fieldvalue'] = GetFormatedDate($logArray[$mycolkey]); + } + else if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_NUMBER ) + { + // Special style classes and colours for SYSLOG_FACILITY + if ( $mycolkey == SYSLOG_FACILITY ) + { + if ( isset($logArray[$mycolkey][SYSLOG_FACILITY]) && strlen($logArray[$mycolkey][SYSLOG_FACILITY]) > 0) + { + // Set Human readable Facility! + $content['syslogmessages'][$counter][$mycolkey]['fieldvalue'] = GetFacilityDisplayName( $logArray[$mycolkey] ); + } + } + else if ( $mycolkey == SYSLOG_SEVERITY ) + { + if ( isset($logArray[$mycolkey][SYSLOG_SEVERITY]) && strlen($logArray[$mycolkey][SYSLOG_SEVERITY]) > 0) + { + // Set Human readable Facility! + $content['syslogmessages'][$counter][$mycolkey]['fieldvalue'] = GetSeverityDisplayName( $logArray[$mycolkey] ); + } + } + else if ( $mycolkey == SYSLOG_MESSAGETYPE ) + { + if ( isset($logArray[$mycolkey][SYSLOG_MESSAGETYPE]) ) + { + // Set Human readable Facility! + $content['syslogmessages'][$counter][$mycolkey]['fieldvalue'] = GetMessageTypeDisplayName( $logArray[$mycolkey] ); + } + } + } + /* + else if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_STRING ) + { + } + */ + } + } + // --- + + // Increment Counter + $counter++; + } while ($counter < $content['ViewEntriesPerPage'] && ($ret = $stream->ReadNext($uID, $logArray)) == SUCCESS); + + if ( $content['read_direction'] == EnumReadDirection::Forward ) + { + // Back Button was clicked, so we need to flip the array + $content['syslogmessages'] = array_reverse ( $content['syslogmessages'] ); + } +// DEBUG +//print_r ( $content['syslogmessages'] ); + } + } + else + { + // This will disable to Main SyslogView and show an error message + $content['error_occured'] = true; + $content['error_details'] = GetErrorMessage($res); + if ( isset($extraErrorDescription) ) + $content['error_details'] .= "

" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); + } + + // Close file! + $stream->Close(); + } + else + { + $content['error_occured'] = true; + $content['error_details'] = GetAndReplaceLangStr( $content['LN_GEN_ERROR_SOURCENOTFOUND'], $currentSourceID); + } +} +// --- + +// --- Convert and Output +if ( $content['error_occured'] ) +{ + InitTemplateParser(); + $page -> parser($content, "export.html"); + $page -> output(); +} +else +{ + // Create a CVS File! + $szOutputContent = ""; + $szOutputMimeType = "text/plain"; + $szOutputCharset = ""; + + $szOutputFileName = "ExportMessages"; + $szOutputFileExtension = ".txt"; + if ( $content['exportformat'] == EXPORT_CVS ) + { + // Set MIME TYPE and File Extension + $szOutputMimeType = "text/csv"; + $szOutputFileExtension = ".csv"; + + // Set Column line in cvs file! + foreach($content['Columns'] as $mycolkey) + { + if ( isset($fields[$mycolkey]) ) + { + // Prepend Comma if needed + if (strlen($szOutputContent) > 0) + $szOutputContent .= ","; + + // Append column name + $szOutputContent .= $content[ $fields[$mycolkey]['FieldCaptionID'] ]; + } + } + + // Append line break + $szOutputContent .= "\n"; + + // Append messages into output + foreach ( $content['syslogmessages'] as $myIndex => $mySyslogMessage ) + { + $szLine = ""; + + // --- Process columns + foreach($mySyslogMessage as $myColkey => $mySyslogField) + { + // Prepend Comma if needed + if (strlen($szLine) > 0) + $szLine .= ","; + + // Append field contents + $szLine .= '"' . str_replace('"', '\\"', $mySyslogField['fieldvalue']) . '"'; + } + // --- + + // Append line! + $szOutputContent .= $szLine . "\n"; + } + } + else if ( $content['exportformat'] == EXPORT_XML ) + { + // Set MIME TYPE and File Extension + $szOutputMimeType = "application/xml"; + $szOutputFileExtension = ".xml"; + $szOutputCharset = "charset=UTF-8"; + + // Create XML Header and first node!! + $szOutputContent .= "\xef\xbb\xbf"; + $szOutputContent .= "\n"; + $szOutputContent .= "\n"; + + // Append messages into output + foreach ( $content['syslogmessages'] as $myIndex => $mySyslogMessage ) + { + $szXmlLine = "\t\n"; + + // --- Process columns + foreach($mySyslogMessage as $myColkey => $mySyslogField) + { + +// if ( isset($content[ $fields[$mycolkey]['FieldCaptionID'] ]) ) +// $szNodeTitle = $content[ $fields[$mycolkey]['FieldCaptionID'] ]; +// else + + // Append field content | first run htmlentities,tnen utf8 encoding!! + $szXmlLine .= "\t\t<" . $myColkey . ">" . utf8_encode( htmlentities($mySyslogField['fieldvalue']) ) . "\n"; + } + // --- + + $szXmlLine .= "\t\n"; + + // Append line! + $szOutputContent .= $szXmlLine; + } + + // End first XML Node + $szOutputContent .= ""; + } + else if ( $content['exportformat'] == EXPORT_PDF ) + { + // Set MIME TYPE and File Extension + $szOutputMimeType = "application/pdf"; + $szOutputFileExtension = ".pdf"; + } + + // Set needed Header properties + header('Content-type: ' . $szOutputMimeType . "; " . $szOutputCharset); + header('Content-Disposition: attachment; filename="' . $szOutputFileName . $szOutputFileExtension . '"'); + + // Output Content! + print( $szOutputContent ); +} +// --- + +?> \ No newline at end of file diff --git a/src/include/functions_common.php b/src/include/functions_common.php index a8504a3..e2329a9 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -334,8 +334,8 @@ function CreateExportFormatList() // Add basic formats! $content['EXPORTTYPES'][EXPORT_CVS] = array( "ID" => EXPORT_CVS, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_CVS'] ); - $content['EXPORTTYPES'][EXPORT_HTML] = array( "ID" => EXPORT_HTML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_HTML'] ); - $content['EXPORTTYPES'][EXPORT_EXCEL] = array( "ID" => EXPORT_EXCEL, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_EXCEL'] ); +//TODO $content['EXPORTTYPES'][EXPORT_HTML] = array( "ID" => EXPORT_HTML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_HTML'] ); +//TODO $content['EXPORTTYPES'][EXPORT_EXCEL] = array( "ID" => EXPORT_EXCEL, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_EXCEL'] ); // Add formats by loaded extensions if ( $content['XML_IS_ENABLED'] ) diff --git a/src/index.php b/src/index.php index 0b2c0a3..bc44410 100644 --- a/src/index.php +++ b/src/index.php @@ -180,7 +180,7 @@ if ( (isset($_POST['search']) || isset($_GET['search'])) || (isset($_POST['filte // --- // --- BEGIN Custom Code -if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$currentSourceID]['SourceType'] == SOURCE_DISK ) +if ( isset($content['Sources'][$currentSourceID]) ) { // Obtain and get the Config Object $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; @@ -704,6 +704,11 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['Sources'][$c // Close file! $stream->Close(); } +else +{ + $content['syslogmessagesenabled'] = "false"; + $content['detailederror'] = GetAndReplaceLangStr( $content['LN_GEN_ERROR_SOURCENOTFOUND'], $currentSourceID); +} // --- // --- Parsen and Output diff --git a/src/lang/en/main.php b/src/lang/en/main.php index eeeb968..c8c3a16 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -79,13 +79,15 @@ $content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; - $content['LN_GEN_SELECTEXPORT'] = "Select to Export"; + $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; $content['LN_GEN_EXPORT_HTML'] = "HTML"; $content['LN_GEN_EXPORT_XML'] = "XML"; $content['LN_GEN_EXPORT_PDF'] = "PDF"; $content['LN_GEN_EXPORT_EXCEL'] = "Excel"; - + $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; + $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; + $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/export.html b/src/templates/export.html new file mode 100644 index 0000000..fc79f7c --- /dev/null +++ b/src/templates/export.html @@ -0,0 +1,17 @@ + + + +

+
+
+
{LN_GEN_ERROR_EXPORING} - {LN_GEN_ERRORDETAILS}
+

{error_details}

+
+

+ {LN_GEN_ERRORRETURNPREV} +
+

+ + + + \ No newline at end of file diff --git a/src/templates/index.html b/src/templates/index.html index 75db873..cc55e9c 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -98,7 +98,7 @@
From 2aebf307fbcbec00d3373786b08bdab8808bf89f Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 5 Aug 2008 11:22:59 +0200 Subject: [PATCH 054/142] Removed experimental PDF export, because the php extensions is to buggy --- src/export.php | 45 ------------------------------- src/include/constants_general.php | 1 - src/include/functions_common.php | 7 +---- 3 files changed, 1 insertion(+), 52 deletions(-) diff --git a/src/export.php b/src/export.php index 3565cc1..4297baa 100644 --- a/src/export.php +++ b/src/export.php @@ -409,51 +409,6 @@ else // End first XML Node $szOutputContent .= ""; } - else if ( $content['exportformat'] == EXPORT_PDF ) - { - // Set MIME TYPE and File Extension - $szOutputMimeType = "application/pdf"; - $szOutputFileExtension = ".pdf"; - - try - { - // Init PDF Document - $myPdf = new PDFlib(); - if ($myPdf->begin_document("", "") == 0) { - die("Error: " . $myPdf->get_errmsg()); - } - - $myPdf->set_info("Creator", "hello.php"); - $myPdf->set_info("Author", "Rainer Schaaf"); - $myPdf->set_info("Title", "Hello world (PHP)!"); - - $myPdf->begin_page_ext(595, 842, ""); - - $font = $myPdf->load_font("Helvetica-Bold", "winansi", ""); - - $myPdf->setfont($font, 24.0); - $myPdf->set_text_pos(50, 700); - - $myPdf->show("Hello world!"); - $myPdf->continue_text("(says PHP)"); - $myPdf->end_page_ext(""); - $myPdf->end_document(""); - - // Copy PDF Output - $szOutputContent = $myPdf->get_buffer(); - } - catch (PDFlibException $e) { - die("PDFlib exception occurred in hello sample:\n" . - "[" . $e->get_errnum() . "] " . $e->get_apiname() . ": " . - $e->get_errmsg() . "\n"); - } - catch (Exception $e) { - die($e); - } - - // Delete PDF Object! - $myPdf = 0; - } // Set needed Header properties header('Content-type: ' . $szOutputMimeType . "; " . $szOutputCharset); diff --git a/src/include/constants_general.php b/src/include/constants_general.php index 09bb19e..9ce4070 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -72,7 +72,6 @@ define('SOURCE_PDO', '3'); // --- Exportformat defines define('EXPORT_CVS', 'CVS'); define('EXPORT_XML', 'XML'); -define('EXPORT_PDF', 'PDF'); // --- // --- diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 58cc70c..51fc636 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -336,10 +336,7 @@ function CreateExportFormatList() $content['EXPORTTYPES'][EXPORT_CVS] = array( "ID" => EXPORT_CVS, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_CVS'] ); $content['EXPORTTYPES'][EXPORT_XML] = array( "ID" => EXPORT_XML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_XML'] ); - // Add formats by loaded extensions - if ( $content['PDF_IS_ENABLED'] ) - $content['EXPORTTYPES'][EXPORT_PDF] = array( "ID" => EXPORT_PDF, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_PDF'] ); - + // TODO: Add formats by loaded extensions } function CreatePredefinedSearches() @@ -405,8 +402,6 @@ function CheckAndSetRunMode() if ( in_array("mysql", $loadedExtensions) ) { $content['MYSQL_IS_ENABLED'] = true; } else { $content['MYSQL_IS_ENABLED'] = false; } // Check PDO Extension if ( in_array("PDO", $loadedExtensions) ) { $content['PDO_IS_ENABLED'] = true; } else { $content['PDO_IS_ENABLED'] = false; } - // Check PDF Extension - if ( in_array("pdf", $loadedExtensions) ) { $content['PDF_IS_ENABLED'] = true; } else { $content['PDF_IS_ENABLED'] = false; } } From d9abe61068eb98294f567b8283007cc8330b191f Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 5 Aug 2008 11:37:44 +0200 Subject: [PATCH 055/142] Removed some minor bugs in the new export functions. XML and CVS Export now fully works --- src/export.php | 2 ++ src/index.php | 17 +++++++++++++++-- src/lang/de/main.php | 1 - src/lang/en/main.php | 1 - src/lang/pt_BR/main.php | 1 - src/templates/index.html | 8 +++++++- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/export.php b/src/export.php index 4297baa..452fcd3 100644 --- a/src/export.php +++ b/src/export.php @@ -79,12 +79,14 @@ if ( { $content['exportformat'] = $_GET['exporttype']; +/* // Check for extensions if ( $content['exportformat'] == EXPORT_PDF && !$content['PDF_IS_ENABLED'] ) { $content['error_occured'] = true; $content['error_details'] = $content['LN_GEN_ERROR_PDFMISSINGEXTENSION']; } +*/ } else { diff --git a/src/index.php b/src/index.php index bc44410..c0f1dbd 100644 --- a/src/index.php +++ b/src/index.php @@ -63,6 +63,9 @@ if ( isset($_GET['uid']) ) else $content['uid_current'] = UID_UNKNOWN; +// copy needed for export function +$content['uid_original'] = $content['uid_current']; + // --- Set Autoreload as meta refresh if ( $content['uid_current'] == UID_UNKNOWN ) { @@ -73,11 +76,21 @@ if ( $content['uid_current'] == UID_UNKNOWN ) else $content['ViewEnableAutoReloadSeconds_visible'] = false; -// Read direction parameter -if ( isset($_GET['direction']) && $_GET['direction'] == "desc" ) +// --- Read direction parameter +if ( isset($_GET['direction']) ) +{ + // Copy to content array + $content['direction'] = $_GET['direction']; +} +else + $content['direction'] = ""; + +// Check for reading direction +if ( $content['direction'] == "desc" ) $content['read_direction'] = EnumReadDirection::Forward; else $content['read_direction'] = EnumReadDirection::Backward; +// --- // If direction is DESC, should we SKIP one? if ( isset($_GET['skipone']) && $_GET['skipone'] == "true" ) diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 4ba1c81..a66c956 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -86,7 +86,6 @@ $content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden. $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; - $content['LN_GEN_ERROR_PDFMISSINGEXTENSION'] = "The PDF Extension is missing in your php environment."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 98ec3f9..fd83cde 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -86,7 +86,6 @@ $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, ma $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; - $content['LN_GEN_ERROR_PDFMISSINGEXTENSION'] = "The PDF Extension is missing in your php environment."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 9f57af0..2a27725 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -90,7 +90,6 @@ $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; - $content['LN_GEN_ERROR_PDFMISSINGEXTENSION'] = "The PDF Extension is missing in your php environment."; // Topmenu Entries diff --git a/src/templates/index.html b/src/templates/index.html index f14702a..e710640 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -97,7 +97,13 @@ + + + - - - - + + + + + + + + + diff --git a/src/templates/admin/admin_upgrade.html b/src/templates/admin/admin_upgrade.html new file mode 100644 index 0000000..923d8bd --- /dev/null +++ b/src/templates/admin/admin_upgrade.html @@ -0,0 +1,110 @@ + + + +

+
+
+
{LN_GEN_ERRORDETAILS}
+

{ERROR_MSG}

+
+

+ {LN_GEN_ERRORRETURNPREV} +
+

+ + +
- - @@ -126,16 +147,16 @@ {LN_GEN_RECORDCOUNT}: {main_recordcount} {LN_GEN_PAGERSIZE}: -
- @@ -144,7 +165,6 @@
-
Pager:   - @@ -360,15 +360,15 @@ -
- -
-

{LN_ERROR_NORECORDS} - {LN_GEN_ERRORDETAILS}

- {detailederror} -
-
-
-
+

+
+
+
{LN_ERROR_NORECORDS} - {LN_GEN_ERRORDETAILS}
+

{detailederror}

+
+

+
+

From 4505ba1bf3175a9b56a266d5aa1751f773f1d47b Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 4 Aug 2008 17:46:27 +0200 Subject: [PATCH 053/142] Started adding PDF Support --- src/export.php | 49 +++++++++++++++++++++++++++++++ src/include/constants_general.php | 2 -- src/include/functions_common.php | 32 ++++---------------- src/lang/de/main.php | 8 +++++ src/lang/en/main.php | 3 +- src/lang/pt_BR/main.php | 8 +++++ src/templates/index.html | 4 +-- 7 files changed, 73 insertions(+), 33 deletions(-) diff --git a/src/export.php b/src/export.php index a7a4f9b..3565cc1 100644 --- a/src/export.php +++ b/src/export.php @@ -76,7 +76,16 @@ if ( (isset($_GET['op']) && $_GET['op'] == "export") && (isset($_GET['exporttype']) && array_key_exists($_GET['exporttype'], $content['EXPORTTYPES'])) ) +{ $content['exportformat'] = $_GET['exporttype']; + + // Check for extensions + if ( $content['exportformat'] == EXPORT_PDF && !$content['PDF_IS_ENABLED'] ) + { + $content['error_occured'] = true; + $content['error_details'] = $content['LN_GEN_ERROR_PDFMISSINGEXTENSION']; + } +} else { $content['error_occured'] = true; @@ -405,10 +414,50 @@ else // Set MIME TYPE and File Extension $szOutputMimeType = "application/pdf"; $szOutputFileExtension = ".pdf"; + + try + { + // Init PDF Document + $myPdf = new PDFlib(); + if ($myPdf->begin_document("", "") == 0) { + die("Error: " . $myPdf->get_errmsg()); + } + + $myPdf->set_info("Creator", "hello.php"); + $myPdf->set_info("Author", "Rainer Schaaf"); + $myPdf->set_info("Title", "Hello world (PHP)!"); + + $myPdf->begin_page_ext(595, 842, ""); + + $font = $myPdf->load_font("Helvetica-Bold", "winansi", ""); + + $myPdf->setfont($font, 24.0); + $myPdf->set_text_pos(50, 700); + + $myPdf->show("Hello world!"); + $myPdf->continue_text("(says PHP)"); + $myPdf->end_page_ext(""); + $myPdf->end_document(""); + + // Copy PDF Output + $szOutputContent = $myPdf->get_buffer(); + } + catch (PDFlibException $e) { + die("PDFlib exception occurred in hello sample:\n" . + "[" . $e->get_errnum() . "] " . $e->get_apiname() . ": " . + $e->get_errmsg() . "\n"); + } + catch (Exception $e) { + die($e); + } + + // Delete PDF Object! + $myPdf = 0; } // Set needed Header properties header('Content-type: ' . $szOutputMimeType . "; " . $szOutputCharset); + header("Content-Length: " . strlen($szOutputContent) ); header('Content-Disposition: attachment; filename="' . $szOutputFileName . $szOutputFileExtension . '"'); // Output Content! diff --git a/src/include/constants_general.php b/src/include/constants_general.php index 0a68e27..09bb19e 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -72,9 +72,7 @@ define('SOURCE_PDO', '3'); // --- Exportformat defines define('EXPORT_CVS', 'CVS'); define('EXPORT_XML', 'XML'); -define('EXPORT_EXCEL', 'EXCEL'); define('EXPORT_PDF', 'PDF'); -define('EXPORT_HTML', 'HTML'); // --- // --- diff --git a/src/include/functions_common.php b/src/include/functions_common.php index e2329a9..58cc70c 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -334,12 +334,9 @@ function CreateExportFormatList() // Add basic formats! $content['EXPORTTYPES'][EXPORT_CVS] = array( "ID" => EXPORT_CVS, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_CVS'] ); -//TODO $content['EXPORTTYPES'][EXPORT_HTML] = array( "ID" => EXPORT_HTML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_HTML'] ); -//TODO $content['EXPORTTYPES'][EXPORT_EXCEL] = array( "ID" => EXPORT_EXCEL, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_EXCEL'] ); + $content['EXPORTTYPES'][EXPORT_XML] = array( "ID" => EXPORT_XML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_XML'] ); // Add formats by loaded extensions - if ( $content['XML_IS_ENABLED'] ) - $content['EXPORTTYPES'][EXPORT_XML] = array( "ID" => EXPORT_XML, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_XML'] ); if ( $content['PDF_IS_ENABLED'] ) $content['EXPORTTYPES'][EXPORT_PDF] = array( "ID" => EXPORT_PDF, "Selected" => "", "DisplayName" => $content['LN_GEN_EXPORT_PDF'] ); @@ -405,31 +402,12 @@ function CheckAndSetRunMode() $content['GD_IS_ENABLED'] = false; // Check MYSQL Extension - if ( in_array("mysql", $loadedExtensions) ) - $content['MYSQL_IS_ENABLED'] = true; - else - $content['MYSQL_IS_ENABLED'] = false; - + if ( in_array("mysql", $loadedExtensions) ) { $content['MYSQL_IS_ENABLED'] = true; } else { $content['MYSQL_IS_ENABLED'] = false; } // Check PDO Extension - if ( in_array("PDO", $loadedExtensions) ) - $content['PDO_IS_ENABLED'] = true; - else - $content['PDO_IS_ENABLED'] = false; - // --- - - // Check XML Extension - if ( in_array("xml", $loadedExtensions) ) - $content['XML_IS_ENABLED'] = true; - else - $content['XML_IS_ENABLED'] = false; - // --- - + if ( in_array("PDO", $loadedExtensions) ) { $content['PDO_IS_ENABLED'] = true; } else { $content['PDO_IS_ENABLED'] = false; } // Check PDF Extension - if ( in_array("pdf", $loadedExtensions) ) - $content['PDF_IS_ENABLED'] = true; - else - $content['PDF_IS_ENABLED'] = false; - // --- + if ( in_array("pdf", $loadedExtensions) ) { $content['PDF_IS_ENABLED'] = true; } else { $content['PDF_IS_ENABLED'] = false; } + } function InitRuntimeInformations() diff --git a/src/lang/de/main.php b/src/lang/de/main.php index bfbed70..4ba1c81 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -79,6 +79,14 @@ $content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden. $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; + $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; + $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; + $content['LN_GEN_EXPORT_XML'] = "XML"; + $content['LN_GEN_EXPORT_PDF'] = "PDF"; + $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; + $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; + $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; + $content['LN_GEN_ERROR_PDFMISSINGEXTENSION'] = "The PDF Extension is missing in your php environment."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index c8c3a16..98ec3f9 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -81,13 +81,12 @@ $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; - $content['LN_GEN_EXPORT_HTML'] = "HTML"; $content['LN_GEN_EXPORT_XML'] = "XML"; $content['LN_GEN_EXPORT_PDF'] = "PDF"; - $content['LN_GEN_EXPORT_EXCEL'] = "Excel"; $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; + $content['LN_GEN_ERROR_PDFMISSINGEXTENSION'] = "The PDF Extension is missing in your php environment."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 4f66f2d..9f57af0 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -83,6 +83,14 @@ $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; + $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; + $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; + $content['LN_GEN_EXPORT_XML'] = "XML"; + $content['LN_GEN_EXPORT_PDF'] = "PDF"; + $content['LN_GEN_ERROR_EXPORING'] = "Error exporting data"; + $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; + $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; + $content['LN_GEN_ERROR_PDFMISSINGEXTENSION'] = "The PDF Extension is missing in your php environment."; // Topmenu Entries diff --git a/src/templates/index.html b/src/templates/index.html index cc55e9c..f14702a 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -99,9 +99,9 @@
- + + + + + + + "); @@ -241,6 +242,31 @@ function OutputDebugMessage($szDbg) print(""); print("
" . $szDbg . "

"); } + + // Check if the user wants to syslog the error! + if ( GetConfigSetting("MiscDebugToSyslog", 0, CFGLEVEL_GLOBAL) == 1 ) + { + syslog(GetPriorityFromDebugLevel($szDbgLevel), $szDbg); + } +} + +function GetPriorityFromDebugLevel( $DebugLevel ) +{ + switch ( $DebugLevel ) + { + case DEBUG_ULTRADEBUG: + return LOG_DEBUG; + case DEBUG_DEBUG: + return LOG_INFO; + case DEBUG_INFO: + return LOG_NOTICE; + case DEBUG_WARN: + return LOG_WARNING; + case DEBUG_ERROR: + return LOG_ERR; + case DEBUG_ERROR_WTF: + return LOG_CRIT; + } } ?> \ No newline at end of file diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index 210c36d..38a15dc 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -83,7 +83,8 @@ $content['LN_GEN_GLOBALVALUE'] = "Global value"; $content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; - +$content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; +$content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 210c36d..38a15dc 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -83,7 +83,8 @@ $content['LN_GEN_GLOBALVALUE'] = "Global value"; $content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; - +$content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; +$content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; // User Center $content['LN_USER_CENTER'] = "User Options"; diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index c04ceb3..451a150 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -53,7 +53,8 @@ $content['LN_GEN_GLOBALVALUE'] = "Global value"; $content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; $content['LN_GEN_DISABLEUSEROPTIONS'] = "Click here to disable personal options"; $content['LN_GEN_ENABLEUSEROPTIONS'] = "Click here to enable personal options"; - +$content['LN_ADMIN_GLOBALONLY'] = "Global Options Only"; +$content['LN_GEN_DEBUGTOSYSLOG'] = "Send Debug to local syslog server"; // General Options $content['LN_ADMIN_GLOBFRONTEND'] = "Global frontend options"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index 77a0151..ca06bc4 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -211,12 +211,17 @@
+ {LN_ADMIN_GLOBALONLY}
{LN_GEN_DEBUGUSERLOGIN} 
{LN_GEN_DEBUGTOSYSLOG}
From ead118f166f6b7cc68066c14f4bc57e00cb33796 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 28 Aug 2008 15:04:11 +0200 Subject: [PATCH 065/142] Added changelog entry --- ChangeLog | 8 ++++++++ src/include/functions_common.php | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 9862593..65abd39 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,12 @@ --------------------------------------------------------------------------- +Version 2.5.5 (devel), 2008-08-28 +- Added option to send debug messages (warnings and error's) from + phpLogCon to the local syslog server on linux. On Windows, the debug + messages will appear in the application event log. +- Enhanced the PDO Logstream Driver for better performance on large + databases. On MYSQL and POSTGRES, the PDO Logstream does not uses the + LIMIT statement to minimize database usage. +--------------------------------------------------------------------------- Version 2.3.10 (beta), 2008-08-27 - Fixed a few parsing issues with prior RFC 3164 syslog messages. These messages are now correctly parsed, or better do not cause diff --git a/src/include/functions_common.php b/src/include/functions_common.php index d9c23da..7d4f1ac 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -66,7 +66,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.5.4"; +$content['BUILDNUMBER'] = "2.5.5"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! From 527abd30dae78be20a36f0330059f0e314a76fde Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 1 Sep 2008 16:39:18 +0200 Subject: [PATCH 066/142] Started implementing msg parsers --- src/classes/logstreamlineparser.class.php | 2 +- .../logstreamlineparsersyslog.class.php | 2 +- .../logstreamlineparserwinsyslog.class.php | 2 +- src/classes/msgparser.class.php | 60 ++++++++++++ .../msgparsers/msgparser.eventlog.class.php | 93 +++++++++++++++++++ src/include/constants_errors.php | 1 + 6 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/classes/msgparser.class.php create mode 100644 src/classes/msgparsers/msgparser.eventlog.class.php diff --git a/src/classes/logstreamlineparser.class.php b/src/classes/logstreamlineparser.class.php index ea1d4ce..deef7cf 100644 --- a/src/classes/logstreamlineparser.class.php +++ b/src/classes/logstreamlineparser.class.php @@ -57,4 +57,4 @@ abstract class LogStreamLineParser { } -?> +?> \ No newline at end of file diff --git a/src/classes/logstreamlineparsersyslog.class.php b/src/classes/logstreamlineparsersyslog.class.php index 7d66b12..8827da3 100644 --- a/src/classes/logstreamlineparsersyslog.class.php +++ b/src/classes/logstreamlineparsersyslog.class.php @@ -135,4 +135,4 @@ class LogStreamLineParsersyslog extends LogStreamLineParser { } -?> +?> \ No newline at end of file diff --git a/src/classes/logstreamlineparserwinsyslog.class.php b/src/classes/logstreamlineparserwinsyslog.class.php index c3b8996..93c3a5b 100644 --- a/src/classes/logstreamlineparserwinsyslog.class.php +++ b/src/classes/logstreamlineparserwinsyslog.class.php @@ -105,4 +105,4 @@ class LogStreamLineParserwinsyslog extends LogStreamLineParser { } -?> +?> \ No newline at end of file diff --git a/src/classes/msgparser.class.php b/src/classes/msgparser.class.php new file mode 100644 index 0000000..bb4696b --- /dev/null +++ b/src/classes/msgparser.class.php @@ -0,0 +1,60 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * LogStream MSGParser abstract basic class * + * * + * All directives are explained within this file * + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- Basic Includes +require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'include/constants_errors.php'); +require_once($gl_root_path . 'include/constants_logstream.php'); +// --- + + +abstract class MsgParser{ +// protected $_arrProperties = null; + + /** + * ParseLine + * + * @param arrArguments array in&out: properties of interest. There can be no guarantee the logstream can actually deliver them. + * @return integer Error stat + */ + public abstract function ParseMsg($szMsg, &$arrArguments); + +} + +?> \ No newline at end of file diff --git a/src/classes/msgparsers/msgparser.eventlog.class.php b/src/classes/msgparsers/msgparser.eventlog.class.php new file mode 100644 index 0000000..f3bd444 --- /dev/null +++ b/src/classes/msgparsers/msgparser.eventlog.class.php @@ -0,0 +1,93 @@ + www.phplogcon.org <- * + * ----------------------------------------------------------------- * + * EventLog MSG Parser is used to split EventLog fields if found + * in the msg + * * + * All directives are explained within this file * + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution. + ********************************************************************* +*/ + +// --- Avoid directly accessing this file! +if ( !defined('IN_PHPLOGCON') ) +{ + die('Hacking attempt'); + exit; +} +// --- + +// --- Basic Includes +require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'include/constants_errors.php'); +require_once($gl_root_path . 'include/constants_logstream.php'); +// --- + + +class MsgParserEventLog extends MsgParser { +// protected $_arrProperties = null; + + // Constructor + public function LogStreamLineParserwinsyslog() { + return; // Nothing + } + + /** + * ParseLine + * + * @param arrArguments array in&out: properties of interest. There can be no guarantee the logstream can actually deliver them. + * @return integer Error stat + */ + public function ParseMsg($szMsg, &$arrArguments) + { + global $content; + + // Set IUT Property first! + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_Syslog; + +/* + // Sample (WinSyslog/EventReporter): 2008-04-02,15:19:06,2008-04-02,15:19:06,127.0.0.1,16,5,EvntSLog: Performance counters for the RSVP (QoS RSVP) service were loaded successfully. + if ( preg_match("/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),(.*?),([0-9]{1,2}),([0-9]{1,2}),(.*?):(.*?)$/", $szMsg, $out ) ) + { + // Copy parsed properties! + $arrArguments[SYSLOG_DATE] = GetEventTime($out[1]); + $arrArguments[SYSLOG_HOST] = $out[3]; + $arrArguments[SYSLOG_FACILITY] = $out[4]; + $arrArguments[SYSLOG_SEVERITY] = $out[5]; + $arrArguments[SYSLOG_SYSLOGTAG] = $out[6]; + $arrArguments[SYSLOG_MESSAGE] = $out[7]; + } + else +*/ + { + // return no match in this case! + return ERROR_MSG_NOMATCH; + } + + // If we reached this position, return success! + return SUCCESS; + } +} + +?> \ No newline at end of file diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index f3e1361..6323849 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -59,6 +59,7 @@ define('ERROR_DB_INVALIDDBMAPPING', 14); define('ERROR_DB_INVALIDDBDRIVER', 16); define('ERROR_DB_TABLENOTFOUND', 17); +define('ERROR_MSG_NOMATCH', 18); ?> From 378006eb149f415eab4451c21c5e49ff19e4be54 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Mon, 1 Sep 2008 17:31:06 +0200 Subject: [PATCH 067/142] Changed WinSyslog line parser, so it is more variable --- src/classes/logstreamlineparserwinsyslog.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/logstreamlineparserwinsyslog.class.php b/src/classes/logstreamlineparserwinsyslog.class.php index 93c3a5b..342b030 100644 --- a/src/classes/logstreamlineparserwinsyslog.class.php +++ b/src/classes/logstreamlineparserwinsyslog.class.php @@ -66,7 +66,7 @@ class LogStreamLineParserwinsyslog extends LogStreamLineParser { $arrArguments[SYSLOG_MESSAGETYPE] = IUT_Syslog; // Sample (WinSyslog/EventReporter): 2008-04-02,15:19:06,2008-04-02,15:19:06,127.0.0.1,16,5,EvntSLog: Performance counters for the RSVP (QoS RSVP) service were loaded successfully. - if ( preg_match("/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),(.*?),([0-9]{1,2}),([0-9]{1,2}),(.*?):(.*?)$/", $szLine, $out ) ) + if ( preg_match("/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2}.[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2}.[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),(.*?),([0-9]{1,2}),([0-9]{1,2}),(.*?):(.*?)$/", $szLine, $out ) ) { // Copy parsed properties! $arrArguments[SYSLOG_DATE] = GetEventTime($out[1]); @@ -76,7 +76,7 @@ class LogStreamLineParserwinsyslog extends LogStreamLineParser { $arrArguments[SYSLOG_SYSLOGTAG] = $out[6]; $arrArguments[SYSLOG_MESSAGE] = $out[7]; } - else if ( preg_match("/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),(.*?),([0-9]{1,2}),([0-9]{1,2}),(.*?)$/", $szLine, $out ) ) + else if ( preg_match("/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2}.[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2}.[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),(.*?),([0-9]{1,2}),([0-9]{1,2}),(.*?)$/", $szLine, $out ) ) { // Copy parsed properties! $arrArguments[SYSLOG_DATE] = GetEventTime($out[1]); From c5b7ff8e3acaedc42f37cf43adcc99e601a491aa Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 2 Sep 2008 14:47:26 +0200 Subject: [PATCH 068/142] Implemented MsgParser interface, and first interface parser. Also implemented database upgrade routine, because we hjave the first database upgrade now ;)! --- src/admin/sources.php | 20 +- src/admin/upgrade.php | 195 ++++++++++++++++++ src/classes/logstream.class.php | 12 +- src/classes/logstreamconfig.class.php | 83 +++++++- src/classes/logstreamdb.class.php | 3 + src/classes/logstreamdisk.class.php | 8 +- src/classes/logstreampdo.class.php | 3 + .../msgparsers/msgparser.eventlog.class.php | 30 +-- src/convert.php | 2 +- src/include/config.sample.php | 2 + src/include/db_template.txt | 3 +- src/include/db_update_v2.txt | 6 + src/include/functions_common.php | 27 ++- src/include/functions_config.php | 7 + src/include/functions_db.php | 2 +- src/include/functions_users.php | 23 ++- src/install.php | 2 +- src/lang/de/admin.php | 17 +- src/lang/de/main.php | 1 + src/lang/en/admin.php | 20 +- src/lang/en/main.php | 1 + src/lang/pt_BR/admin.php | 17 +- src/lang/pt_BR/main.php | 2 + src/templates/admin/admin_sources.html | 4 + src/templates/admin/admin_upgrade.html | 110 ++++++++++ 25 files changed, 547 insertions(+), 53 deletions(-) create mode 100644 src/admin/upgrade.php create mode 100644 src/include/db_update_v2.txt create mode 100644 src/templates/admin/admin_upgrade.html diff --git a/src/admin/sources.php b/src/admin/sources.php index 080bd13..1b2e460 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -67,6 +67,7 @@ if ( isset($_GET['op']) ) $content['Name'] = ""; $content['SourceType'] = SOURCE_DISK; CreateSourceTypesList($content['SourceType']); + $content['MsgParserList'] = ""; // Init View List! $content['SourceViewID'] = 'SYSLOG'; @@ -131,6 +132,7 @@ if ( isset($_GET['op']) ) $content['Name'] = $mysource['Name']; $content['SourceType'] = $mysource['SourceType']; CreateSourceTypesList($content['SourceType']); + $content['MsgParserList'] = $mysource['MsgParserList']; // Init View List! $content['SourceViewID'] = $mysource['ViewID']; @@ -259,6 +261,7 @@ if ( isset($_POST['op']) ) if ( isset($_POST['id']) ) { $content['SOURCEID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SOURCEID'] = -1; } if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } + if ( isset($_POST['MsgParserList']) ) { $content['MsgParserList'] = DB_RemoveBadChars($_POST['MsgParserList']); } if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } if ( isset($content['SourceType']) ) @@ -408,6 +411,7 @@ if ( isset($_POST['op']) ) $tmpSource['ID'] = $content['SOURCEID']; $tmpSource['Name'] = $content['Name']; $tmpSource['SourceType']= $content['SourceType']; + $tmpSource['MsgParserList']= $content['MsgParserList']; $tmpSource['ViewID'] = $content['SourceViewID']; if ( $tmpSource['SourceType'] == SOURCE_DISK ) { @@ -455,9 +459,10 @@ if ( isset($_POST['op']) ) // Add custom search now! if ( $content['SourceType'] == SOURCE_DISK ) { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, LogLineType, DiskFile, userid, groupid) + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, ViewID, LogLineType, DiskFile, userid, groupid) VALUES ('" . $content['Name'] . "', " . $content['SourceType'] . ", + '" . $content['MsgParserList'] . "', '" . $content['SourceViewID'] . "', '" . $content['SourceLogLineType'] . "', '" . $content['SourceDiskFile'] . "', @@ -467,9 +472,10 @@ if ( isset($_POST['op']) ) } else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) VALUES ('" . $content['Name'] . "', " . $content['SourceType'] . ", + '" . $content['MsgParserList'] . "', '" . $content['SourceViewID'] . "', '" . $content['SourceDBTableType'] . "', " . $content['SourceDBType'] . ", @@ -507,6 +513,7 @@ if ( isset($_POST['op']) ) $sqlquery = "UPDATE " . DB_SOURCES . " SET Name = '" . $content['Name'] . "', SourceType = " . $content['SourceType'] . ", + MsgParserList = '" . $content['MsgParserList'] . "', ViewID = '" . $content['SourceViewID'] . "', LogLineType = '" . $content['SourceLogLineType'] . "', DiskFile = '" . $content['SourceDiskFile'] . "', @@ -519,6 +526,7 @@ if ( isset($_POST['op']) ) $sqlquery = "UPDATE " . DB_SOURCES . " SET Name = '" . $content['Name'] . "', SourceType = " . $content['SourceType'] . ", + MsgParserList = '" . $content['MsgParserList'] . "', ViewID = '" . $content['SourceViewID'] . "', DBTableType = '" . $content['SourceDBTableType'] . "', DBType = " . $content['SourceDBType'] . ", @@ -625,6 +633,14 @@ if ( !isset($_POST['op']) && !isset($_GET['op']) ) // --- // print_r ( $content['SOURCES'] ); } + +/* +* Helper function to read and init available msg parsers +*/ +function ReadMsgParserList() +{ + global $gl_root_path, $content; +} // --- END Custom Code // --- BEGIN CREATE TITLE diff --git a/src/admin/upgrade.php b/src/admin/upgrade.php new file mode 100644 index 0000000..a38e3c1 --- /dev/null +++ b/src/admin/upgrade.php @@ -0,0 +1,195 @@ + Helps administrating phplogcon datasources + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +//include($gl_root_path . 'include/functions_filters.php'); + +// Set Upgrade Page! +define('IS_UPRGADEPAGE', true); +$content['IS_UPRGADEPAGE'] = true; + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// *** *** // + +// --- BEGIN Custom Code +if ( isset($content['database_forcedatabaseupdate']) && $content['database_forcedatabaseupdate'] == "yes" ) +{ + if ( isset($_GET['op']) ) + { + if ($_GET['op'] == "upgrade") + { + // Lets start the uodating! + $content['UPGRADE_RUNNING'] = "1"; + + $content['sql_sucess'] = 0; + $content['sql_failed'] = 0; + $totaldbdefs = ""; + + $tblPref = GetConfigSetting("UserDBPref", "logcon"); + + // +1 so we start at the right DB Version! + for( $i = $content['database_installedversion']+1; $i <= $content['database_internalversion']; $i++ ) + { + $myfilename = "db_update_v" . $i . ".txt"; + + // Lets read the table definitions :) + $handle = @fopen($content['BASEPATH'] . "include/" . $myfilename, "r"); + if ($handle === false) + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_DBUPGRADE_DBFILENOTFOUND'], $myfilename ); + } + else + { + while (!feof($handle)) + { + $buffer = fgets($handle, 4096); + + $pos = strpos($buffer, "--"); + if ($pos === false) + $totaldbdefs .= $buffer; + else if ( $pos > 2 && strlen( trim($buffer) ) > 1 ) + $totaldbdefs .= $buffer; + } + fclose($handle); + } + } + + if ( !isset($content['ISERROR']) ) + { + if ( strlen($totaldbdefs) <= 0 ) + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = $content['LN_DBUPGRADE_DBDEFFILESHORT']; + } + + // Replace stats_ with the custom one ;) + $totaldbdefs = str_replace( "`logcon_", "`" . $tblPref, $totaldbdefs ); + + // Now split by sql command + $mycommands = split( ";\r\n", $totaldbdefs ); + + // check for different linefeed + if ( count($mycommands) <= 1 ) + $mycommands = split( ";\n", $totaldbdefs ); + + //Still only one? Abort + if ( count($mycommands) <= 1 ) + { + $content['ISERROR'] = "true"; + $content['ERROR_MSG'] = $content['LN_DBUPGRADE_DBDEFFILESHORT']; + } + + if ( !isset($content['ISERROR']) ) + { + // --- Now execute all commands + ini_set('error_reporting', E_WARNING); // Enable Warnings! + + for($i = 0; $i < count($mycommands); $i++) + { + if ( strlen(trim($mycommands[$i])) > 1 ) + { + $result = DB_Query( $mycommands[$i], false ); + if ($result == FALSE) + { + $content['failedstatements'][ $content['sql_failed'] ]['myerrmsg'] = DB_ReturnSimpleErrorMsg(); + $content['failedstatements'][ $content['sql_failed'] ]['mystatement'] = $mycommands[$i]; + + // --- Set CSS Class + if ( $content['sql_failed'] % 2 == 0 ) + $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line1"; + else + $content['failedstatements'][ $content['sql_failed'] ]['cssclass'] = "line2"; + // --- + + $content['sql_failed']++; + } + else + $content['sql_sucess']++; + + // Free result + DB_FreeQuery($result); + } + } + // --- + + // --- Upgrade Database Version in Config Table + $content['database_installedversion'] = $content['database_internalversion']; + WriteConfigValue( "database_installedversion", true ); + // --- + } + } + } + else + $content['UPGRADE_DEFAULT'] = "1"; + } + else + $content['UPGRADE_DEFAULT'] = "1"; + + +} +else + $content['UPGRADE_DEFAULT'] = "0"; + + +// disable running to be save! ;) +if ( isset($content['ISERROR']) ) + $content['UPGRADE_RUNNING'] = "0"; +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_DBUPGRADE_TITLE']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_upgrade.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index 79f0115..a14c5c2 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -40,6 +40,7 @@ if ( !defined('IN_PHPLOGCON') ) // --- Basic Includes require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'classes/msgparser.class.php'); require_once($gl_root_path . 'include/constants_errors.php'); require_once($gl_root_path . 'include/constants_logstream.php'); // --- @@ -204,6 +205,14 @@ abstract class LogStream { */ public abstract function IsPropertySortable($myProperty); + /* + * Helper functino to trigger initialisation of MsgParsers + */ + public function RunBasicInits() + { + $this->_logStreamConfigObj->InitMsgParsers(); + } + /** * Set the filter for the current stream. * @@ -582,7 +591,6 @@ abstract class LogStream { return -1; } - } -?> +?> \ No newline at end of file diff --git a/src/classes/logstreamconfig.class.php b/src/classes/logstreamconfig.class.php index e02cc2d..dce1a3b 100644 --- a/src/classes/logstreamconfig.class.php +++ b/src/classes/logstreamconfig.class.php @@ -48,8 +48,89 @@ abstract class LogStreamConfig { protected $_logStreamName = ''; protected $_defaultFacility = ''; protected $_defaultSeverity = ''; - + + // helpers properties for message parser list! + protected $_msgParserList = null; // Contains a string list of configure msg parsers + protected $_msgParserObjList = null; // Contains an object reference list to the msg parsers + + // Constructor prototype public abstract function LogStreamFactory($o); + + /* + * Initialize Msg Parsers! + */ + public function InitMsgParsers() + { + // Init parsers if available and not initialized already! + if ( $this->_msgParserList != null && $this->_msgParserObjList == null ) + { + // Loop through parsers + foreach( $this->_msgParserList as $szParser ) + { + // Set Classname + $szClassName = "MsgParser_" . $szParser; + + // Create OBjectRef! + $this->_msgParserObjList[] = new $szClassName(); + } + } + } + + /* + * + */ + public function SetMsgParserList( $szParsers ) + { + global $gl_root_path; + + // Check if we have at least something to check + if ( $szParsers == null || strlen($szParsers) <= 0 ) + return; + + // Set list of Parsers! + if ( strpos($szParsers, ",") ) + $aParsers = explode( ",", $szParsers ); + else + $aParsers[0] = $szParsers; + + // Loop through parsers + foreach( $aParsers as $szParser ) + { + // Remove whitespaces + $szParser = trim($szParser); + + // Check if parser file include exists + $szIncludeFile = $gl_root_path . 'classes/msgparsers/msgparser.' . $szParser . '.class.php'; + if ( file_exists($szIncludeFile) ) + { + // Try to include + if ( @include_once($szIncludeFile) ) + $this->_msgParserList[] = $szParser; + else + OutputDebugMessage("Error, MsgParser '" . $szParser . "' could not be included. ", DEBUG_ERROR); + + } + } + +// print_r ( $this->_msgParserList ); + } + + public function ProcessMsgParsers($szMsg, &$arrArguments) + { + // Process if set! + if ( $this->_msgParserObjList != null ) + { + foreach( $this->_msgParserObjList as $myMsgParser ) + { + // Perform Parsing, and return if was successfull! Otherwise the next Parser will be called. + if ( $myMsgParser->ParseMsg($szMsg, $arrArguments) == SUCCESS ) + return SUCCESS; + } + } + + // reached this means all work is done! + return SUCCESS; + } } ?> diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 4532141..3f21eea 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -84,6 +84,9 @@ class LogStreamDB extends LogStream { { global $dbmapping; + // Initialise Basic stuff within the Classs + $this->RunBasicInits(); + // Verify database connection (This also opens the database!) $res = $this->Verify(); if ( $res != SUCCESS ) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index dbfed7e..c135df7 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -73,7 +73,10 @@ class LogStreamDisk extends LogStream { * @param arrProperties array in: Properties wish list. * @return integer Error stat */ - public function Open($arrProperties) { + public function Open($arrProperties) + { + // Initialise Basic stuff within the Classs + $this->RunBasicInits(); // Check if file exists! $result = $this->Verify(); @@ -235,6 +238,9 @@ class LogStreamDisk extends LogStream { { // Line Parser Hook here $this->_logStreamConfigObj->_lineParser->ParseLine($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + + // Run optional Message Parsers now + $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); // Set uID to the PropertiesOut! $arrProperitesOut[SYSLOG_UID] = $uID; diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index e499ff4..8142c10 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -87,6 +87,9 @@ class LogStreamPDO extends LogStream { { global $dbmapping; + // Initialise Basic stuff within the Classs + $this->RunBasicInits(); + // Verify database driver and connection (This also opens the database!) $res = $this->Verify(); if ( $res != SUCCESS ) diff --git a/src/classes/msgparsers/msgparser.eventlog.class.php b/src/classes/msgparsers/msgparser.eventlog.class.php index f3bd444..fa22d78 100644 --- a/src/classes/msgparsers/msgparser.eventlog.class.php +++ b/src/classes/msgparsers/msgparser.eventlog.class.php @@ -40,16 +40,16 @@ if ( !defined('IN_PHPLOGCON') ) // --- Basic Includes require_once($gl_root_path . 'classes/enums.class.php'); +require_once($gl_root_path . 'classes/msgparser.class.php'); require_once($gl_root_path . 'include/constants_errors.php'); require_once($gl_root_path . 'include/constants_logstream.php'); // --- - -class MsgParserEventLog extends MsgParser { +class MsgParser_eventlog extends MsgParser { // protected $_arrProperties = null; // Constructor - public function LogStreamLineParserwinsyslog() { + public function MsgParser_eventlog() { return; // Nothing } @@ -63,28 +63,28 @@ class MsgParserEventLog extends MsgParser { { global $content; - // Set IUT Property first! - $arrArguments[SYSLOG_MESSAGETYPE] = IUT_Syslog; - -/* - // Sample (WinSyslog/EventReporter): 2008-04-02,15:19:06,2008-04-02,15:19:06,127.0.0.1,16,5,EvntSLog: Performance counters for the RSVP (QoS RSVP) service were loaded successfully. - if ( preg_match("/([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),([0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2},[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}),(.*?),([0-9]{1,2}),([0-9]{1,2}),(.*?):(.*?)$/", $szMsg, $out ) ) + // Sample (WinSyslog/EventReporter): 7035,XPVS2005\Administrator,Service Control Manager,System,[INF],0,The Adiscon EvntSLog service was successfully sent a start control. + // Source: %id%,%user%,%sourceproc%,%NTEventLogType%,%severity%,%category%,%msg%%$CRLF% + if ( preg_match("/([0-9]{1,12}),(.*?),(.*?),(.*?),(.*?),([0-9]{1,12}),(.*?)$/", $szMsg, $out ) ) { // Copy parsed properties! - $arrArguments[SYSLOG_DATE] = GetEventTime($out[1]); - $arrArguments[SYSLOG_HOST] = $out[3]; - $arrArguments[SYSLOG_FACILITY] = $out[4]; - $arrArguments[SYSLOG_SEVERITY] = $out[5]; - $arrArguments[SYSLOG_SYSLOGTAG] = $out[6]; + $arrArguments[SYSLOG_EVENT_ID] = $out[1]; + $arrArguments[SYSLOG_EVENT_USER] = $out[2]; + $arrArguments[SYSLOG_EVENT_SOURCE] = $out[3]; + $arrArguments[SYSLOG_EVENT_LOGTYPE] = $out[4]; +/// $arrArguments[SYSLOG_SEVERITY] = $out[5]; + $arrArguments[SYSLOG_EVENT_CATEGORY] = $out[6]; $arrArguments[SYSLOG_MESSAGE] = $out[7]; } else -*/ { // return no match in this case! return ERROR_MSG_NOMATCH; } + // Set IUT Property if success! + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_NT_EventReport; + // If we reached this position, return success! return SUCCESS; } diff --git a/src/convert.php b/src/convert.php index 09d46a2..c24a2d4 100644 --- a/src/convert.php +++ b/src/convert.php @@ -162,7 +162,7 @@ else if ( $content['CONVERT_STEP'] == 3 ) } // Append INSERT Statement for Config Table to set the Database Version ^^! - $mycommands[count($mycommands)] = "INSERT INTO `" . GetConfigSetting("UserDBPref") . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', 1)"; + $mycommands[count($mycommands)] = "INSERT INTO `" . GetConfigSetting("UserDBPref") . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', " . $content['database_internalversion'] . ")"; // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 7fb20f2..34c7a93 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -104,12 +104,14 @@ $CFG['Search'][] = array ( "DisplayName" => "All messages from last 31 days", "S $CFG['Sources']['Source1']['Name'] = "Syslog Disk File"; $CFG['Sources']['Source1']['SourceType'] = SOURCE_DISK; $CFG['Sources']['Source1']['LogLineType'] = "syslog"; + $CFG['Sources']['Source1']['MsgParserList'] = ""; $CFG['Sources']['Source1']['DiskFile'] = "/var/log/syslog"; $CFG['Sources']['Source1']['ViewID'] = "SYSLOG"; $CFG['Sources']['Source2']['ID'] = "Source5"; $CFG['Sources']['Source2']['Name'] = "WinSyslog DB"; $CFG['Sources']['Source2']['SourceType'] = SOURCE_DB; + $CFG['Sources']['Source1']['MsgParserList'] = ""; $CFG['Sources']['Source2']['DBTableType'] = "winsyslog"; $CFG['Sources']['Source2']['DBType'] = DB_MYSQL; $CFG['Sources']['Source2']['DBServer'] = "localhost"; diff --git a/src/include/db_template.txt b/src/include/db_template.txt index b146a33..79eb09b 100644 --- a/src/include/db_template.txt +++ b/src/include/db_template.txt @@ -60,10 +60,11 @@ CREATE TABLE IF NOT EXISTS `logcon_searches` ( -- DROP TABLE IF EXISTS `logcon_sources`; -CREATE TABLE IF NOT EXISTS `logcon_sources` ( +CREATE TABLE `logcon_sources` ( `ID` int(11) NOT NULL auto_increment, `Name` varchar(255) NOT NULL, `SourceType` tinyint(4) NOT NULL, + `MsgParserList` varchar(255) NOT NULL, `ViewID` varchar(64) NOT NULL, `LogLineType` varchar(64) default NULL, `DiskFile` varchar(255) default NULL, diff --git a/src/include/db_update_v2.txt b/src/include/db_update_v2.txt new file mode 100644 index 0000000..d84a18c --- /dev/null +++ b/src/include/db_update_v2.txt @@ -0,0 +1,6 @@ +-- New Database Structure Updates +ALTER TABLE `logcon_sources` ADD `MsgParserList` VARCHAR( 255 ) NOT NULL AFTER `SourceType` ; + +-- Insert data + +-- Updated Data diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 7d4f1ac..94c11d8 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -40,13 +40,13 @@ if ( !defined('IN_PHPLOGCON') ) // --- // --- Basic Includes -include($gl_root_path . 'include/constants_general.php'); -include($gl_root_path . 'include/constants_logstream.php'); +include_once($gl_root_path . 'include/constants_general.php'); +include_once($gl_root_path . 'include/constants_logstream.php'); -include($gl_root_path . 'classes/class_template.php'); -include($gl_root_path . 'include/functions_themes.php'); -include($gl_root_path . 'include/functions_db.php'); -include($gl_root_path . 'include/functions_config.php'); +include_once($gl_root_path . 'classes/class_template.php'); +include_once($gl_root_path . 'include/functions_themes.php'); +include_once($gl_root_path . 'include/functions_db.php'); +include_once($gl_root_path . 'include/functions_config.php'); // --- // --- Define Basic vars @@ -551,6 +551,13 @@ function InitConfigurationValues() else // Critical ERROR HERE! DieWithFriendlyErrorMsg( "Critical Error occured while trying to access the database in table '" . DB_CONFIG . "'" ); + // Database Version Checker! + if ( $content['database_internalversion'] > $content['database_installedversion'] ) + { + // Database is out of date, we need to upgrade + $content['database_forcedatabaseupdate'] = "yes"; + } + // Now we init the user session stuff InitUserSession(); @@ -579,14 +586,6 @@ function InitConfigurationValues() // Load Configured Sources LoadSourcesFromDatabase(); - - - // Database Version Checker! - if ( $content['database_internalversion'] > $content['database_installedversion'] ) - { - // Database is out of date, we need to upgrade - $content['database_forcedatabaseupdate'] = "yes"; - } } else { diff --git a/src/include/functions_config.php b/src/include/functions_config.php index a6d71d5..0aa6fea 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -67,6 +67,12 @@ function InitSource(&$mysource) $CFG['Sources'][$iSourceID]['groupid'] = null; $content['Sources'][$iSourceID]['groupid'] = null; } + + if ( !isset($mysource['MsgParserList']) ) + { + $CFG['Sources'][$iSourceID]['MsgParserList'] = null; + $content['Sources'][$iSourceID]['MsgParserList'] = null; + } // --- // Set default view id to source @@ -165,6 +171,7 @@ function InitSource(&$mysource) // Set generic configuration options $mysource['ObjRef']->_pageCount = GetConfigSetting("ViewEntriesPerPage", 50); + $mysource['ObjRef']->SetMsgParserList( $mysource['MsgParserList'] ); // Set default SourceID here! if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) diff --git a/src/include/functions_db.php b/src/include/functions_db.php index 2bb2c17..ea3839d 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -45,7 +45,7 @@ $errdesc = ""; $errno = 0; // --- Current Database Version, this is important for automated database Updates! -$content['database_internalversion'] = "1"; // Whenever incremented, a database upgrade is needed +$content['database_internalversion'] = "2"; // Whenever incremented, a database upgrade is needed $content['database_installedversion'] = "0"; // 0 is default which means Prior Versioning Database // --- diff --git a/src/include/functions_users.php b/src/include/functions_users.php index 7597760..00be277 100644 --- a/src/include/functions_users.php +++ b/src/include/functions_users.php @@ -97,18 +97,15 @@ function InitUserSession() else // Critical ERROR HERE! DieWithFriendlyErrorMsg( "Critical Error occured while trying to access the database in table '" . DB_CONFIG . "'" ); // --- + + // --- Extracheck for available database updates! + if ( isset($content['database_forcedatabaseupdate']) && $content['database_forcedatabaseupdate'] == "yes" && !defined('IS_UPRGADEPAGE') ) + RedirectToDatabaseUpgrade(); + // --- // Successfully logged in return true; } -/* - // New, Check for database Version and may redirect to updatepage! - if ( isset($content['database_forcedatabaseupdate']) && - $content['database_forcedatabaseupdate'] == "yes" && - $isUpgradePage == false - ) - RedirectToDatabaseUpgrade(); -*/ } else { @@ -189,12 +186,16 @@ function CheckUserLogin( $username, $password ) $_SESSION['SESSION_GROUPIDS'] = $content['SESSION_GROUPIDS']; // --- - // ---Set LASTLOGIN Time! $result = DB_Query("UPDATE " . DB_USERS . " SET last_login = " . time() . " WHERE ID = " . $content['SESSION_USERID']); DB_FreeQuery($result); // --- + // --- Extracheck for available database updates! + if ( isset($content['database_forcedatabaseupdate']) && $content['database_forcedatabaseupdate'] == "yes" && !defined('IS_UPRGADEPAGE') ) + RedirectToDatabaseUpgrade(); + // --- + // Success ! return true; } @@ -236,12 +237,14 @@ function RedirectToUserLogin() function RedirectToDatabaseUpgrade() { + global $content; + // build referer $referer = $_SERVER['PHP_SELF']; if ( isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0 ) $referer .= "?" . $_SERVER['QUERY_STRING']; - header("Location: upgrade.php?referer=" . urlencode($referer) ); + header("Location: " . $content['BASEPATH'] . "admin/upgrade.php?referer=" . urlencode($referer) ); exit; } // --- END Usermanagement Function --- diff --git a/src/install.php b/src/install.php index ffaffb4..f2e70bc 100644 --- a/src/install.php +++ b/src/install.php @@ -394,7 +394,7 @@ else if ( $content['INSTALL_STEP'] == 5 ) } // Append INSERT Statement for Config Table to set the Database Version ^^! - $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', 1)"; + $mycommands[count($mycommands)] = "INSERT INTO `" . $_SESSION["UserDBPref"] . "config` (`propname`, `propvalue`, `is_global`) VALUES ('database_installedversion', '" . $content['database_internalversion'] . "', " . $content['database_internalversion'] . ")"; // --- Now execute all commands ini_set('error_reporting', E_WARNING); // Enable Warnings! diff --git a/src/lang/de/admin.php b/src/lang/de/admin.php index 38a15dc..09322b0 100644 --- a/src/lang/de/admin.php +++ b/src/lang/de/admin.php @@ -166,7 +166,7 @@ $content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id ' $content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; $content['LN_SEARCH_'] = ""; -// Custom Searches center +// Custom Views center $content['LN_VIEWS_CENTER'] = "Views Options"; $content['LN_VIEWS_ID'] = "ID"; $content['LN_VIEWS_NAME'] = "View Name"; @@ -190,6 +190,7 @@ $content['LN_VIEWS_ERROR_NOCOLUMNS'] = "You need to add at least one column in o $content['LN_VIEWS_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; $content['LN_VIEWS_'] = ""; +// Custom Sources center $content['LN_SOURCES_CENTER'] = "Sources Options"; $content['LN_SOURCES_EDIT'] = "Edit Source"; $content['LN_SOURCES_DELETE'] = "Delete Source"; @@ -217,5 +218,19 @@ $content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' fa $content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; $content['LN_SOURCES_'] = ""; +// Database Upgrade +$content['LN_DBUPGRADE_TITLE'] = "phpLogCon Database Update"; +$content['LN_DBUPGRADE_DBFILENOTFOUND'] = "The database upgrade file '%1' could not be found in the include folder! Please check if all files were successfully uploaded."; +$content['LN_DBUPGRADE_DBDEFFILESHORT'] = "The database upgrade files where empty or did not contain any SQL Command! Please check if all files were successfully uploaded."; +$content['LN_DBUPGRADE_WELCOME'] = "Welcome to the database upgrade"; +$content['LN_DBUPGRADE_BEFORESTART'] = "Before you start upgrading your database, you should create a FULL BACKUP OF YOUR DATABASE. Anything else will be done automatically by the upgrade Script."; +$content['LN_DBUPGRADE_CURRENTINSTALLED'] = "Current Installed Database Version"; +$content['LN_DBUPGRADE_TOBEINSTALLED'] = "Do be Installed Database Version"; +$content['LN_DBUPGRADE_HASBEENDONE'] = "Database Update has been performed, see the results below"; +$content['LN_DBUPGRADE_SUCCESSEXEC'] = "Successfully executed statements"; +$content['LN_DBUPGRADE_FAILEDEXEC'] = "Failed statements"; +$content['LN_DBUPGRADE_ONESTATEMENTFAILED'] = "At least one statement failed, you may need to correct and fix this issue manually. See error details below"; +$content['LN_DBUPGRADE_ERRMSG'] = "Error Message"; +$content['LN_DBUPGRADE_ULTRASTATSDBVERSION'] = "phpLogCon Database Version"; ?> \ No newline at end of file diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 19afd8f..80356cd 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -190,6 +190,7 @@ $content['LN_CFG_NAMEOFTHESOURCE'] = "Name der Quelle"; $content['LN_CFG_FIRSTSYSLOGSOURCE'] = "Erste Syslog Quelle"; $content['LN_CFG_VIEW'] = "Select View"; $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; + $content['LN_CFG_MSGPARSERS'] = "Message Parsers (comma seperated)"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Details für syslog-Nachrichten mit der ID"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 38a15dc..299c8ba 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -49,7 +49,6 @@ $content['LN_GEN_USERONLY_LONG'] = "For me only
(Only available to your user $content['LN_GEN_GROUPONLY_LONG'] = "For this group
(Only available to the selected group)"; $content['LN_GEN_GROUPONLYNAME'] = "Group '%1'"; - // General Options $content['LN_ADMIN_GLOBFRONTEND'] = "Global frontend options"; $content['LN_ADMIN_USERFRONTEND'] = "User specific frontend options"; @@ -166,7 +165,7 @@ $content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id ' $content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; $content['LN_SEARCH_'] = ""; -// Custom Searches center +// Custom Views center $content['LN_VIEWS_CENTER'] = "Views Options"; $content['LN_VIEWS_ID'] = "ID"; $content['LN_VIEWS_NAME'] = "View Name"; @@ -190,6 +189,7 @@ $content['LN_VIEWS_ERROR_NOCOLUMNS'] = "You need to add at least one column in o $content['LN_VIEWS_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; $content['LN_VIEWS_'] = ""; +// Custom Sources center $content['LN_SOURCES_CENTER'] = "Sources Options"; $content['LN_SOURCES_EDIT'] = "Edit Source"; $content['LN_SOURCES_DELETE'] = "Delete Source"; @@ -217,5 +217,21 @@ $content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' fa $content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; $content['LN_SOURCES_'] = ""; +// Database Upgrade +$content['LN_DBUPGRADE_TITLE'] = "phpLogCon Database Update"; +$content['LN_DBUPGRADE_DBFILENOTFOUND'] = "The database upgrade file '%1' could not be found in the include folder! Please check if all files were successfully uploaded."; +$content['LN_DBUPGRADE_DBDEFFILESHORT'] = "The database upgrade files where empty or did not contain any SQL Command! Please check if all files were successfully uploaded."; +$content['LN_DBUPGRADE_WELCOME'] = "Welcome to the database upgrade"; +$content['LN_DBUPGRADE_BEFORESTART'] = "Before you start upgrading your database, you should create a FULL BACKUP OF YOUR DATABASE. Anything else will be done automatically by the upgrade Script."; +$content['LN_DBUPGRADE_CURRENTINSTALLED'] = "Current Installed Database Version"; +$content['LN_DBUPGRADE_TOBEINSTALLED'] = "Do be Installed Database Version"; +$content['LN_DBUPGRADE_HASBEENDONE'] = "Database Update has been performed, see the results below"; +$content['LN_DBUPGRADE_SUCCESSEXEC'] = "Successfully executed statements"; +$content['LN_DBUPGRADE_FAILEDEXEC'] = "Failed statements"; +$content['LN_DBUPGRADE_ONESTATEMENTFAILED'] = "At least one statement failed, you may need to correct and fix this issue manually. See error details below"; +$content['LN_DBUPGRADE_ERRMSG'] = "Error Message"; +$content['LN_DBUPGRADE_ULTRASTATSDBVERSION'] = "phpLogCon Database Version"; + + ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index a191238..4bb7129 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -190,6 +190,7 @@ $content['LN_CFG_FIRSTSYSLOGSOURCE'] = "First Syslog Source"; $content['LN_CFG_DBROWCOUNTING'] = "Enable Row Counting"; $content['LN_CFG_VIEW'] = "Select View"; $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; +$content['LN_CFG_MSGPARSERS'] = "Message Parsers (comma seperated)"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Details for the syslog messages with id"; diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index 451a150..bc7a549 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -165,7 +165,7 @@ $content['LN_SEARCH_ERROR_DELSEARCH'] = "Deleting of the Custom Search with id ' $content['LN_SEARCH_ERROR_HASBEENDEL'] = "The Custom Search '%1' has been successfully deleted!"; $content['LN_SEARCH_'] = ""; -// Custom Searches center +// Custom Views center $content['LN_VIEWS_CENTER'] = "Views Options"; $content['LN_VIEWS_ID'] = "ID"; $content['LN_VIEWS_NAME'] = "View Name"; @@ -189,6 +189,7 @@ $content['LN_VIEWS_ERROR_NOCOLUMNS'] = "You need to add at least one column in o $content['LN_VIEWS_HASBEENEDIT'] = "The Custom Search '%1' has been successfully edited."; $content['LN_VIEWS_'] = ""; +// Custom Sources center $content['LN_SOURCES_CENTER'] = "Sources Options"; $content['LN_SOURCES_EDIT'] = "Edit Source"; $content['LN_SOURCES_DELETE'] = "Delete Source"; @@ -216,5 +217,19 @@ $content['LN_SOURCES_ERROR_DELSOURCE'] = "Deleting of the Source with id '%1' fa $content['LN_SOURCES_ERROR_HASBEENDEL'] = "The Source '%1' has been successfully deleted!"; $content['LN_SOURCES_'] = ""; +// Database Upgrade +$content['LN_DBUPGRADE_TITLE'] = "phpLogCon Database Update"; +$content['LN_DBUPGRADE_DBFILENOTFOUND'] = "The database upgrade file '%1' could not be found in the include folder! Please check if all files were successfully uploaded."; +$content['LN_DBUPGRADE_DBDEFFILESHORT'] = "The database upgrade files where empty or did not contain any SQL Command! Please check if all files were successfully uploaded."; +$content['LN_DBUPGRADE_WELCOME'] = "Welcome to the database upgrade"; +$content['LN_DBUPGRADE_BEFORESTART'] = "Before you start upgrading your database, you should create a FULL BACKUP OF YOUR DATABASE. Anything else will be done automatically by the upgrade Script."; +$content['LN_DBUPGRADE_CURRENTINSTALLED'] = "Current Installed Database Version"; +$content['LN_DBUPGRADE_TOBEINSTALLED'] = "Do be Installed Database Version"; +$content['LN_DBUPGRADE_HASBEENDONE'] = "Database Update has been performed, see the results below"; +$content['LN_DBUPGRADE_SUCCESSEXEC'] = "Successfully executed statements"; +$content['LN_DBUPGRADE_FAILEDEXEC'] = "Failed statements"; +$content['LN_DBUPGRADE_ONESTATEMENTFAILED'] = "At least one statement failed, you may need to correct and fix this issue manually. See error details below"; +$content['LN_DBUPGRADE_ERRMSG'] = "Error Message"; +$content['LN_DBUPGRADE_ULTRASTATSDBVERSION'] = "phpLogCon Database Version"; ?> \ No newline at end of file diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 219f341..dd46b5a 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -194,6 +194,8 @@ $content['LN_CFG_NAMEOFTHESOURCE'] = "Nome da origem"; $content['LN_CFG_FIRSTSYSLOGSOURCE'] = "Fonte primária Syslog"; $content['LN_CFG_DBROWCOUNTING'] = "Habilitar contagem de registro"; $content['LN_CFG_VIEW'] = "Selecione visão"; + $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; + $content['LN_CFG_MSGPARSERS'] = "Message Parsers (comma seperated)"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Detalhes para a mensagem com id"; diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index ad835af..be9e2d1 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -122,6 +122,10 @@
{LN_CFG_MSGPARSERS}
{LN_GEN_USERONLY}
+ + + + + + +
{LN_DBUPGRADE_TITLE}
+ + + + + + +
+ +

+

{LN_DBUPGRADE_WELCOME}

+

{LN_DBUPGRADE_BEFORESTART}

+ {LN_DBUPGRADE_CURRENTINSTALLED}: {database_installedversion}
+ {LN_DBUPGRADE_TOBEINSTALLED}: {database_internalversion}

+ +

+
Click on Upgrade to start!
+

 

+ +
+ + + + + + + +
+

+

{LN_DBUPGRADE_HASBEENDONE}:

+ +

  • {LN_DBUPGRADE_SUCCESSEXEC}: {sql_sucess}
    +
  • {LN_DBUPGRADE_FAILEDEXEC}: {sql_failed}
    + + + + + + + + + + + + + + + + +
    {LN_DBUPGRADE_ONESTATEMENTFAILED}
    {LN_DBUPGRADE_ERRMSG}SQL Statement
    {myerrmsg}{mystatement}
    +

     

    + + + + {LN_DBUPGRADE_CURRENTINSTALLED}: {database_installedversion}
    + {LN_DBUPGRADE_TOBEINSTALLED}: {database_internalversion}
    + +

    +
    Click on here to return to the Admin Center
    +

     

    + +
  • + + + + + + + +
    + +

    Welcome to the Database Upgrade

    +

    Your database is fully updated!

    + {LN_DBUPGRADE_CURRENTINSTALLED}: {database_installedversion}
    + {LN_DBUPGRADE_ULTRASTATSDBVERSION}: {database_internalversion}

    + +

    +
    Click on here to return to the Admin Center
    +

     

    + +
    + + +

    + +
    + + \ No newline at end of file From f545c7f38acbf66af37e9c47e99815f3ea04a831 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 2 Sep 2008 14:54:13 +0200 Subject: [PATCH 069/142] Added MsgParser into database logstream classes as well --- src/classes/logstreamdb.class.php | 6 +++++- src/classes/logstreampdo.class.php | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 3f21eea..5d7d857 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -257,6 +257,10 @@ class LogStreamDB extends LogStream { $arrProperitesOut[$property] = ''; } + // Run optional Message Parsers now + if ( isset($arrProperitesOut[SYSLOG_MESSAGE]) ) + $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + // Set uID to the PropertiesOut! //DEBUG -> $this->_currentRecordNum; $uID = $arrProperitesOut[SYSLOG_UID] = $this->bufferedRecords[$this->_currentRecordNum][$dbmapping[$szTableType][SYSLOG_UID]]; @@ -727,7 +731,7 @@ class LogStreamDB extends LogStream { // Append LIMIT clause $szSql .= " LIMIT " . $this->_currentRecordStart . ", " . $this->_logStreamConfigObj->RecordsPerQuery; -echo $szSql . "
    "; +//echo $szSql . "
    "; // Perform Database Query $myquery = mysql_query($szSql, $this->_dbhandle); diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 8142c10..91d6d2d 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -292,6 +292,10 @@ class LogStreamPDO extends LogStream { $arrProperitesOut[$property] = ''; } + // Run optional Message Parsers now + if ( isset($arrProperitesOut[SYSLOG_MESSAGE]) ) + $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + // Set uID to the PropertiesOut! //DEBUG -> $this->_currentRecordNum; $uID = $arrProperitesOut[SYSLOG_UID] = $this->bufferedRecords[$this->_currentRecordNum][$dbmapping[$szTableType][SYSLOG_UID]]; From 018ebaf3c3759fa141af53c76a6bf454859e96e5 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 2 Sep 2008 16:48:05 +0200 Subject: [PATCH 070/142] Added option to normalize messages using the new message parsers Also added support to show line breaks within popup messages, and the detail page --- src/admin/sources.php | 29 ++++++++++++++----- src/classes/logstreamconfig.class.php | 22 +++++++++++--- src/classes/msgparser.class.php | 2 +- .../msgparsers/msgparser.eventlog.class.php | 21 +++++++++++++- src/details.php | 4 +-- src/include/config.sample.php | 1 + src/include/db_template.txt | 1 + src/include/db_update_v2.txt | 1 + src/include/functions_common.php | 7 +++++ src/include/functions_config.php | 7 +++++ src/index.php | 7 +++-- src/lang/de/main.php | 1 + src/lang/en/main.php | 1 + src/lang/pt_BR/main.php | 1 + src/templates/admin/admin_sources.html | 5 ++++ 15 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/admin/sources.php b/src/admin/sources.php index 1b2e460..a75bc05 100644 --- a/src/admin/sources.php +++ b/src/admin/sources.php @@ -68,6 +68,8 @@ if ( isset($_GET['op']) ) $content['SourceType'] = SOURCE_DISK; CreateSourceTypesList($content['SourceType']); $content['MsgParserList'] = ""; + $content['MsgNormalize'] = 0; + $content['CHECKED_ISNORMALIZEMSG'] = ""; // Init View List! $content['SourceViewID'] = 'SYSLOG'; @@ -133,6 +135,11 @@ if ( isset($_GET['op']) ) $content['SourceType'] = $mysource['SourceType']; CreateSourceTypesList($content['SourceType']); $content['MsgParserList'] = $mysource['MsgParserList']; + $content['MsgNormalize'] = $mysource['MsgNormalize']; + if ( $mysource['MsgNormalize'] == 1 ) + $content['CHECKED_ISNORMALIZEMSG'] = "checked"; + else + $content['CHECKED_ISNORMALIZEMSG'] = ""; // Init View List! $content['SourceViewID'] = $mysource['ViewID']; @@ -262,6 +269,7 @@ if ( isset($_POST['op']) ) if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } if ( isset($_POST['MsgParserList']) ) { $content['MsgParserList'] = DB_RemoveBadChars($_POST['MsgParserList']); } + if ( isset($_POST['MsgNormalize']) ) { $content['MsgNormalize'] = intval(DB_RemoveBadChars($_POST['MsgNormalize'])); } else {$content['MsgNormalize'] = 0; } if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } if ( isset($content['SourceType']) ) @@ -408,11 +416,12 @@ if ( isset($_POST['op']) ) include($gl_root_path . 'classes/logstream.class.php'); // First create a tmp source array - $tmpSource['ID'] = $content['SOURCEID']; - $tmpSource['Name'] = $content['Name']; - $tmpSource['SourceType']= $content['SourceType']; - $tmpSource['MsgParserList']= $content['MsgParserList']; - $tmpSource['ViewID'] = $content['SourceViewID']; + $tmpSource['ID'] = $content['SOURCEID']; + $tmpSource['Name'] = $content['Name']; + $tmpSource['SourceType'] = $content['SourceType']; + $tmpSource['MsgParserList'] = $content['MsgParserList']; + $tmpSource['MsgNormalize'] = $content['MsgNormalize']; + $tmpSource['ViewID'] = $content['SourceViewID']; if ( $tmpSource['SourceType'] == SOURCE_DISK ) { $tmpSource['LogLineType'] = $content['SourceLogLineType']; @@ -459,10 +468,11 @@ if ( isset($_POST['op']) ) // Add custom search now! if ( $content['SourceType'] == SOURCE_DISK ) { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, ViewID, LogLineType, DiskFile, userid, groupid) + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, LogLineType, DiskFile, userid, groupid) VALUES ('" . $content['Name'] . "', " . $content['SourceType'] . ", - '" . $content['MsgParserList'] . "', + '" . $content['MsgParserList'] . "', + " . $content['MsgNormalize'] . ", '" . $content['SourceViewID'] . "', '" . $content['SourceLogLineType'] . "', '" . $content['SourceDiskFile'] . "', @@ -472,10 +482,11 @@ if ( isset($_POST['op']) ) } else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) { - $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) VALUES ('" . $content['Name'] . "', " . $content['SourceType'] . ", '" . $content['MsgParserList'] . "', + " . $content['MsgNormalize'] . ", '" . $content['SourceViewID'] . "', '" . $content['SourceDBTableType'] . "', " . $content['SourceDBType'] . ", @@ -514,6 +525,7 @@ if ( isset($_POST['op']) ) Name = '" . $content['Name'] . "', SourceType = " . $content['SourceType'] . ", MsgParserList = '" . $content['MsgParserList'] . "', + MsgNormalize = " . $content['MsgNormalize'] . ", ViewID = '" . $content['SourceViewID'] . "', LogLineType = '" . $content['SourceLogLineType'] . "', DiskFile = '" . $content['SourceDiskFile'] . "', @@ -527,6 +539,7 @@ if ( isset($_POST['op']) ) Name = '" . $content['Name'] . "', SourceType = " . $content['SourceType'] . ", MsgParserList = '" . $content['MsgParserList'] . "', + MsgNormalize = " . $content['MsgNormalize'] . ", ViewID = '" . $content['SourceViewID'] . "', DBTableType = '" . $content['SourceDBTableType'] . "', DBType = " . $content['SourceDBType'] . ", diff --git a/src/classes/logstreamconfig.class.php b/src/classes/logstreamconfig.class.php index dce1a3b..b473ee4 100644 --- a/src/classes/logstreamconfig.class.php +++ b/src/classes/logstreamconfig.class.php @@ -50,8 +50,9 @@ abstract class LogStreamConfig { protected $_defaultSeverity = ''; // helpers properties for message parser list! - protected $_msgParserList = null; // Contains a string list of configure msg parsers - protected $_msgParserObjList = null; // Contains an object reference list to the msg parsers + protected $_msgParserList = null; // Contains a string list of configure msg parsers + protected $_msgParserObjList = null; // Contains an object reference list to the msg parsers + protected $_MsgNormalize = 0; // If set to one, the msg will be reconstructed if successfully parsed before // Constructor prototype public abstract function LogStreamFactory($o); @@ -71,13 +72,26 @@ abstract class LogStreamConfig { $szClassName = "MsgParser_" . $szParser; // Create OBjectRef! - $this->_msgParserObjList[] = new $szClassName(); + $NewParser = new $szClassName(); // Create new instance + $NewParser->_MsgNormalize = $this->_MsgNormalize; // Copy property! + $this->_msgParserObjList[] = $NewParser; // Append NewParser to Parser array } } } /* - * + * Helper function to init Parserlist + */ + public function SetMsgNormalize( $nNewVal ) + { + if ( $nNewVal == 0 ) + $this->_MsgNormalize = 0; + else + $this->_MsgNormalize = 1; + } + + /* + * Helper function to init Parserlist */ public function SetMsgParserList( $szParsers ) { diff --git a/src/classes/msgparser.class.php b/src/classes/msgparser.class.php index bb4696b..f20c78e 100644 --- a/src/classes/msgparser.class.php +++ b/src/classes/msgparser.class.php @@ -45,7 +45,7 @@ require_once($gl_root_path . 'include/constants_logstream.php'); abstract class MsgParser{ -// protected $_arrProperties = null; + public $_MsgNormalize = 0; // If set to one, the msg will be reconstructed if successfully parsed before /** * ParseLine diff --git a/src/classes/msgparsers/msgparser.eventlog.class.php b/src/classes/msgparsers/msgparser.eventlog.class.php index fa22d78..cd54942 100644 --- a/src/classes/msgparsers/msgparser.eventlog.class.php +++ b/src/classes/msgparsers/msgparser.eventlog.class.php @@ -61,7 +61,7 @@ class MsgParser_eventlog extends MsgParser { */ public function ParseMsg($szMsg, &$arrArguments) { - global $content; + global $content, $fields; // Sample (WinSyslog/EventReporter): 7035,XPVS2005\Administrator,Service Control Manager,System,[INF],0,The Adiscon EvntSLog service was successfully sent a start control. // Source: %id%,%user%,%sourceproc%,%NTEventLogType%,%severity%,%category%,%msg%%$CRLF% @@ -75,6 +75,25 @@ class MsgParser_eventlog extends MsgParser { /// $arrArguments[SYSLOG_SEVERITY] = $out[5]; $arrArguments[SYSLOG_EVENT_CATEGORY] = $out[6]; $arrArguments[SYSLOG_MESSAGE] = $out[7]; + + if ( $this->_MsgNormalize == 1 ) + { + // Create Field Array to prepend into msg! Reverse Order here + $myFields = array( SYSLOG_EVENT_CATEGORY, SYSLOG_EVENT_LOGTYPE, SYSLOG_EVENT_SOURCE, SYSLOG_EVENT_USER, SYSLOG_EVENT_ID ); + + foreach ( $myFields as $myField ) + { + // Set Field Caption + if ( isset($content[ $fields[$myField]['FieldCaptionID'] ]) ) + $szFieldName = $content[ $fields[$myField]['FieldCaptionID'] ]; + else + $szFieldName = $fields[$myField]['FieldCaptionID']; + + // Append Field into msg + $arrArguments[SYSLOG_MESSAGE] = $szFieldName . "='" . $arrArguments[$myField] . "' " . $arrArguments[SYSLOG_MESSAGE]; + } + + } } else { diff --git a/src/details.php b/src/details.php index 4e3a4b4..737e086 100644 --- a/src/details.php +++ b/src/details.php @@ -245,9 +245,9 @@ if ( isset($content['Sources'][$currentSourceID]) ) // && $content['uid_current' else if ( $content['fields'][$mycolkey]['FieldType'] == FILTER_TYPE_STRING ) { if ( $mycolkey == SYSLOG_MESSAGE ) - $content['fields'][$mycolkey]['fieldvalue'] = GetStringWithHTMLCodes($logArray[$mycolkey]); + $content['fields'][$mycolkey]['fieldvalue'] = ReplaceLineBreaksInString( GetStringWithHTMLCodes($logArray[$mycolkey]) ); else // kindly copy! - $content['fields'][$mycolkey]['fieldvalue'] = $logArray[$mycolkey]; + $content['fields'][$mycolkey]['fieldvalue'] = ReplaceLineBreaksInString( $logArray[$mycolkey] ); // --- HOOK here to add context links! AddContextLinks($content['fields'][$mycolkey]['fieldvalue']); diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 34c7a93..f2a678f 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -105,6 +105,7 @@ $CFG['Search'][] = array ( "DisplayName" => "All messages from last 31 days", "S $CFG['Sources']['Source1']['SourceType'] = SOURCE_DISK; $CFG['Sources']['Source1']['LogLineType'] = "syslog"; $CFG['Sources']['Source1']['MsgParserList'] = ""; + $CFG['Sources']['Source1']['MsgNormalize'] = 0; $CFG['Sources']['Source1']['DiskFile'] = "/var/log/syslog"; $CFG['Sources']['Source1']['ViewID'] = "SYSLOG"; diff --git a/src/include/db_template.txt b/src/include/db_template.txt index 79eb09b..2eb633d 100644 --- a/src/include/db_template.txt +++ b/src/include/db_template.txt @@ -65,6 +65,7 @@ CREATE TABLE `logcon_sources` ( `Name` varchar(255) NOT NULL, `SourceType` tinyint(4) NOT NULL, `MsgParserList` varchar(255) NOT NULL, + `MsgNormalize` tinyint(1) NOT NULL default '0', `ViewID` varchar(64) NOT NULL, `LogLineType` varchar(64) default NULL, `DiskFile` varchar(255) default NULL, diff --git a/src/include/db_update_v2.txt b/src/include/db_update_v2.txt index d84a18c..ac0bdb5 100644 --- a/src/include/db_update_v2.txt +++ b/src/include/db_update_v2.txt @@ -1,5 +1,6 @@ -- New Database Structure Updates ALTER TABLE `logcon_sources` ADD `MsgParserList` VARCHAR( 255 ) NOT NULL AFTER `SourceType` ; +ALTER TABLE `logcon_sources` ADD `MsgNormalize` BOOL NOT NULL DEFAULT '0' AFTER `MsgParserList` ; -- Insert data diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 94c11d8..2e13104 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -762,8 +762,15 @@ function InitPageTitle() return $szReturn; } +function ReplaceLineBreaksInString($myStr) +{ + // Add linebreaks! + return str_replace( "\n", "
    ", $myStr ); +} + function GetStringWithHTMLCodes($myStr) { + // Replace all special characters with valid html representations return htmlentities($myStr); } diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 0aa6fea..fccf720 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -73,6 +73,12 @@ function InitSource(&$mysource) $CFG['Sources'][$iSourceID]['MsgParserList'] = null; $content['Sources'][$iSourceID]['MsgParserList'] = null; } + + if ( !isset($mysource['MsgNormalize']) ) + { + $CFG['Sources'][$iSourceID]['MsgNormalize'] = 0; + $content['Sources'][$iSourceID]['MsgNormalize'] = 0; + } // --- // Set default view id to source @@ -172,6 +178,7 @@ function InitSource(&$mysource) // Set generic configuration options $mysource['ObjRef']->_pageCount = GetConfigSetting("ViewEntriesPerPage", 50); $mysource['ObjRef']->SetMsgParserList( $mysource['MsgParserList'] ); + $mysource['ObjRef']->SetMsgNormalize( $mysource['MsgNormalize'] ); // Set default SourceID here! if ( isset($content['Sources'][$iSourceID]) && !isset($currentSourceID) ) diff --git a/src/index.php b/src/index.php index f685aa0..e418cd8 100644 --- a/src/index.php +++ b/src/index.php @@ -567,10 +567,13 @@ if ( isset($content['Sources'][$currentSourceID]) ) // If message field, we need to handle differently! if ( $mykey == SYSLOG_MESSAGE ) { + // Get DetailMsg with linebreaks + $szDetailMsg = ReplaceLineBreaksInString(GetStringWithHTMLCodes($logArray[SYSLOG_MESSAGE])); + if ( isset($content['highlightwords']) ) - $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] = HighLightString( $content['highlightwords'],GetStringWithHTMLCodes($logArray[SYSLOG_MESSAGE]) ); + $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] = HighLightString( $content['highlightwords'], $szDetailMsg); else - $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] = GetStringWithHTMLCodes($logArray[SYSLOG_MESSAGE]); + $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] = $szDetailMsg; // --- HOOK here to add context links! AddContextLinks( $content['syslogmessages'][$counter]['values'][$mycolkey]['messagesdetails'][$myIndex]['detailfieldvalue'] ); diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 80356cd..65925fd 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -191,6 +191,7 @@ $content['LN_CFG_FIRSTSYSLOGSOURCE'] = "Erste Syslog Quelle"; $content['LN_CFG_VIEW'] = "Select View"; $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; $content['LN_CFG_MSGPARSERS'] = "Message Parsers (comma seperated)"; + $content['LN_CFG_NORMALIZEMSG'] = "Normalize Message within Parsers"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Details für syslog-Nachrichten mit der ID"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 4bb7129..5294703 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -191,6 +191,7 @@ $content['LN_CFG_DBROWCOUNTING'] = "Enable Row Counting"; $content['LN_CFG_VIEW'] = "Select View"; $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; $content['LN_CFG_MSGPARSERS'] = "Message Parsers (comma seperated)"; +$content['LN_CFG_NORMALIZEMSG'] = "Normalize Message within Parsers"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Details for the syslog messages with id"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index dd46b5a..69680c7 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -196,6 +196,7 @@ $content['LN_CFG_DBROWCOUNTING'] = "Habilitar contagem de registro"; $content['LN_CFG_VIEW'] = "Selecione visão"; $content['LN_CFG_DBUSERLOGINREQUIRED'] = "Require user to be logged in"; $content['LN_CFG_MSGPARSERS'] = "Message Parsers (comma seperated)"; + $content['LN_CFG_NORMALIZEMSG'] = "Normalize Message within Parsers"; // Details page $content['LN_DETAILS_FORSYSLOGMSG'] = "Detalhes para a mensagem com id"; diff --git a/src/templates/admin/admin_sources.html b/src/templates/admin/admin_sources.html index be9e2d1..e115955 100644 --- a/src/templates/admin/admin_sources.html +++ b/src/templates/admin/admin_sources.html @@ -126,6 +126,11 @@
    {LN_CFG_MSGPARSERS}
    {LN_CFG_NORMALIZEMSG}
    {LN_GEN_USERONLY}
    - + + - + diff --git a/src/templates/statistics.html b/src/templates/statistics.html new file mode 100644 index 0000000..8a0be3b --- /dev/null +++ b/src/templates/statistics.html @@ -0,0 +1,37 @@ + + + +

    +
    +
    +
    {LN_GEN_ERRORDETAILS}
    +

    {ERROR_MSG}

    +
    +

    +
    +

    + + +
    {LN_MENU_SEARCH}{LN_MENU_SEARCH} {LN_MENU_SHOWEVENTS}{LN_MENU_STATISTICS} {LN_MENU_HELP}{LN_MENU_HELP} {LN_MENU_SEARCHINKB}
    + + + + + + + +
    {LN_MENU_STATISTICS}
    +

    + + + + +
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSOURCE}
    + + +

    +
    + +
    + + \ No newline at end of file From 38a00af8da69dd5c4c4ea4fcf6a34733f8a4ab8c Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 3 Sep 2008 17:55:00 +0200 Subject: [PATCH 077/142] Started implemented jpgraph lib to draw charts and graphs --- src/chartgenerator.php | 223 ++++++++++++++-------------- src/classes/logstream.class.php | 7 + src/classes/logstreamdb.class.php | 12 ++ src/classes/logstreamdisk.class.php | 43 ++++++ src/classes/logstreampdo.class.php | 12 ++ src/include/constants_general.php | 3 + src/lang/en/main.php | 4 + 7 files changed, 192 insertions(+), 112 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index a1dd9b8..dfe8d1a 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -50,18 +50,39 @@ InitFilterHelpers(); // Helpers for frontend filtering! // --- // --- READ CONTENT Vars +$content['error_occured'] = false; + if ( isset($_GET['type']) ) $content['chart_type'] = intval($_GET['type']); else $content['chart_type'] = CHART_CAKE; if ( isset($_GET['width']) ) +{ $content['chart_width'] = intval($_GET['width']); + + // Limit Chart Size for now + if ( $content['chart_width'] < 100 ) + $content['chart_width'] = 100; + else if ( $content['chart_width'] > 1000 ) + $content['chart_width'] = 1000; +} else $content['chart_width'] = 100; if ( isset($_GET['byfield']) ) - $content['chart_field'] = $_GET['byfield']; +{ + if ( isset($fields[ $_GET['byfield'] ]) ) + { + $content['chart_field'] = $_GET['byfield']; + $content['chart_fieldtype'] = $fields[SYSLOG_UID]['FieldType']; + } + else + { + $content['error_occured'] = true; + $content['error_details'] = $content['LN_GEN_ERROR_INVALIDFIELD']; + } +} else { $content['error_occured'] = true; @@ -74,6 +95,23 @@ $content['TITLE'] = InitPageTitle(); // --- END CREATE TITLE // --- BEGIN Custom Code + +include_once ($gl_root_path . "classes/jpgraph/jpgraph.php"); +include_once ($gl_root_path . "classes/jpgraph/jpgraph_bar.php"); + +// Create Basic Image! +$myGraph = new Graph($content['chart_width'], $content['chart_width'], 'auto'); +$myGraph->SetScale("textlin"); +$myGraph->img->SetMargin(60,30,20,40); +$myGraph->yaxis->SetTitleMargin(45); +$myGraph->yaxis->scale->SetGrace(30); +$myGraph->SetShadow(); + +// Turn the tickmarks +$myGraph->xaxis->SetTickSide(SIDE_DOWN); +$myGraph->yaxis->SetTickSide(SIDE_LEFT); + +// Get data and print on the image! if ( !$content['error_occured'] ) { if ( isset($content['Sources'][$currentSourceID]) ) @@ -87,8 +125,51 @@ if ( !$content['error_occured'] ) $res = $stream->Open( $content['Columns'], true ); if ( $res == SUCCESS ) { + // Obtain data from the logstream! + $chartData = $stream->GetCountSortedByField($content['chart_field'], $content['chart_fieldtype']); + + // If data is valid, we have an array! + if ( is_array($chartData) ) + { + // Sort Array, so the highest count comes first! + array_multisort($chartData, SORT_NUMERIC, SORT_DESC); + + // Create y array! + foreach( $chartData as $myYData) + $YchartData[] = intval($myYData); + + //print_r ($chartData); + //$datay=array(12,26,9,17,31); + + // Create a bar pot + $bplot = new BarPlot($YchartData); +// $bplot->SetFillColor("orange"); + + // Use a shadow on the bar graphs (just use the default settings) + $bplot->SetShadow(); + $bplot->value->SetFormat("%2.0f",70); + $bplot->value->SetFont(FF_ARIAL,FS_NORMAL,9); + $bplot->value->SetColor("blue"); + $bplot->value->Show(); + + $myGraph->Add($bplot); + + $myGraph->title->Set("Chart blabla"); + $myGraph->xaxis->title->Set("X-title"); + $myGraph->yaxis->title->Set("Y-title"); + } + else + { + $content['error_occured'] = true; + $content['error_details'] = GetErrorMessage($chartData); + if ( isset($extraErrorDescription) ) + $content['error_details'] .= "\n\n" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); + } + + +//$fields[SYSLOG_UID]['FieldID'] } else @@ -97,7 +178,7 @@ if ( !$content['error_occured'] ) $content['error_occured'] = true; $content['error_details'] = GetErrorMessage($res); if ( isset($extraErrorDescription) ) - $content['error_details'] .= "

    " . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); + $content['error_details'] .= "\n\n" . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); } // Close file! @@ -109,122 +190,40 @@ if ( !$content['error_occured'] ) $content['error_details'] = GetAndReplaceLangStr( $content['LN_GEN_ERROR_SOURCENOTFOUND'], $currentSourceID); } } -// --- -// --- Convert and Output -if ( $content['error_occured'] ) +if ( $content['error_occured'] ) { - // TODO PRINT ERROR ON PICTURE STREAM! + // QUICK AND DIRTY! + $myImage = imagecreatetruecolor( $content['chart_width'], $content['chart_width']); -// InitTemplateParser(); -// $page -> parser($content, "export.html"); -// $page -> output(); -} -else -{ - // Create ChartDiagram! +/* // create basic colours + $red = ImageColorAllocate($myImage, 255, 0, 0); + $green = ImageColorAllocate($myImage, 0, 255, 0); + $gray = ImageColorAllocate($myImage, 128, 128, 128); + $black = ImageColorAllocate($myImage, 0, 0, 0); + $white = ImageColorAllocate($myImage, 255, 255, 255); + + // Fill image with colour, and create a border + imagerectangle( $myImage, 0, 0, $content['chart_width']-1, $content['chart_width']-1, $gray ); + imagefill( $myImage, 1, 1, $white ); +*/ + $text_color = imagecolorallocate($myImage, 255, 0, 0); + imagestring($myImage, 3, 10, 10, $content['LN_GEN_ERRORDETAILS'], $text_color); + imagestring($myImage, 3, 10, 25, $content['error_details'], $text_color); + + header ("Content-type: image/png"); + imagepng($myImage); // Outputs the image to the browser + imagedestroy($myImage); // Clean Image resource exit; - - // Create a CVS File! - $szOutputContent = ""; - $szOutputMimeType = "text/plain"; - $szOutputCharset = ""; - - $szOutputFileName = "ExportMessages"; - $szOutputFileExtension = ".txt"; - if ( $content['exportformat'] == EXPORT_CVS ) - { - // Set MIME TYPE and File Extension - $szOutputMimeType = "text/csv"; - $szOutputFileExtension = ".csv"; - - // Set Column line in cvs file! - foreach($content['Columns'] as $mycolkey) - { - if ( isset($fields[$mycolkey]) ) - { - // Prepend Comma if needed - if (strlen($szOutputContent) > 0) - $szOutputContent .= ","; - - // Append column name - $szOutputContent .= $content[ $fields[$mycolkey]['FieldCaptionID'] ]; - } - } - - // Append line break - $szOutputContent .= "\n"; - - // Append messages into output - foreach ( $content['syslogmessages'] as $myIndex => $mySyslogMessage ) - { - $szLine = ""; - - // --- Process columns - foreach($mySyslogMessage as $myColkey => $mySyslogField) - { - // Prepend Comma if needed - if (strlen($szLine) > 0) - $szLine .= ","; - - // Append field contents - $szLine .= '"' . str_replace('"', '\\"', $mySyslogField['fieldvalue']) . '"'; - } - // --- - - // Append line! - $szOutputContent .= $szLine . "\n"; - } - } - else if ( $content['exportformat'] == EXPORT_XML ) - { - // Set MIME TYPE and File Extension - $szOutputMimeType = "application/xml"; - $szOutputFileExtension = ".xml"; - $szOutputCharset = "charset=UTF-8"; - - // Create XML Header and first node!! - $szOutputContent .= "\xef\xbb\xbf"; - $szOutputContent .= "\n"; - $szOutputContent .= "\n"; - - // Append messages into output - foreach ( $content['syslogmessages'] as $myIndex => $mySyslogMessage ) - { - $szXmlLine = "\t\n"; - - // --- Process columns - foreach($mySyslogMessage as $myColkey => $mySyslogField) - { - -// if ( isset($content[ $fields[$mycolkey]['FieldCaptionID'] ]) ) -// $szNodeTitle = $content[ $fields[$mycolkey]['FieldCaptionID'] ]; -// else - - // Append field content | first run htmlentities,tnen utf8 encoding!! - $szXmlLine .= "\t\t<" . $myColkey . ">" . utf8_encode( htmlentities($mySyslogField['fieldvalue']) ) . "\n"; - } - // --- - - $szXmlLine .= "\t\n"; - - // Append line! - $szOutputContent .= $szXmlLine; - } - - // End first XML Node - $szOutputContent .= ""; - } - - // Set needed Header properties - header('Content-type: ' . $szOutputMimeType . "; " . $szOutputCharset); - header("Content-Length: " . strlen($szOutputContent) ); - header('Content-Disposition: attachment; filename="' . $szOutputFileName . $szOutputFileExtension . '"'); - - // Output Content! - print( $szOutputContent ); } // --- + +// --- Output the image + +// Send back the HTML page which will call this script again to retrieve the image. +$myGraph->StrokeCSIM(); +// --- + ?> \ No newline at end of file diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index a14c5c2..e567081 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -195,6 +195,13 @@ abstract class LogStream { */ public abstract function GetCurrentPageNumber(); + /** + * This functions is used by charts/graph generator to obtain data + * + * @return integer Error stat + */ + public abstract function GetCountSortedByField($szFieldId, $nFieldType); + /** * Gets a property and checks if the class is able to sort the records diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 5d7d857..dac6810 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -499,6 +499,18 @@ class LogStreamDB extends LogStream { return false; } + /** + * Implementation of GetCountSortedByField + * + * In the native MYSQL Logstream, the database will do most of the work + * + * @return integer Error stat + */ + public function GetCountSortedByField($szFieldId, $nFieldType) + { + } + + /* * ============= Beginn of private functions ============= */ diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index c135df7..e2ba072 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -573,6 +573,49 @@ class LogStreamDisk extends LogStream { return false; } + /** + * Implementation of GetCountSortedByField + * + * For now, the disk source needs to loop through the whole file + * to consolidate and sort the data + * + * @return integer Error stat + */ + public function GetCountSortedByField($szFieldId, $nFieldType) + { + // We loop through all loglines! this may take a while! + $uID = UID_UNKNOWN; + $ret = $this->ReadNext($uID, $logArray); + if ( $ret == SUCCESS ) + { + do + { + if ( isset($logArray[$szFieldId]) ) + { + if ( isset($aResult[ $logArray[$szFieldId] ]) ) + $aResult[ $logArray[$szFieldId] ]++; + else + $aResult[ $logArray[$szFieldId] ] = 1; + /* + if ( isset($aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT]) ) + $aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT]++; + else + { + $aResult[ $logArray[$szFieldId] ][CHARTDATA_NAME] = $logArray[$szFieldId]; + $aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT] = 1; + } + */ + } + } while ( ($ret = $this->ReadNext($uID, $logArray)) == SUCCESS ); + + // finally return result! + return $aResult; + } + else + return ERROR_NOMORERECORDS; + } + + /** * Set the direction the stream should read data. * diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 91d6d2d..7a613cb 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -509,6 +509,18 @@ class LogStreamPDO extends LogStream { return false; } + /** + * Implementation of GetCountSortedByField + * + * In the PDO DB Logstream, the database will do most of the work + * + * @return integer Error stat + */ + public function GetCountSortedByField($szFieldId, $nFieldType) + { + } + + /* * ============= Beginn of private functions ============= */ diff --git a/src/include/constants_general.php b/src/include/constants_general.php index 3421475..a9ef9e1 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -78,6 +78,9 @@ define('EXPORT_XML', 'XML'); define('CHART_CAKE', 1); define('CHART_BARS_VERTICAL', 2); define('CHART_BARS_HORIZONTAL', 3); + +define('CHARTDATA_NAME', 'NAME'); +define('CHARTDATA_COUNT', 'COUNT'); // --- // --- diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 888645b..c8b96d0 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -282,6 +282,10 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the // Stats Site $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; $content['LN_STATS_GRAPH'] = "Graph"; + $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; + $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; + + ?> \ No newline at end of file From 8e271d67963deeeecd9aab8ad7af2aa7a33a0b9d Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 5 Sep 2008 17:22:23 +0200 Subject: [PATCH 078/142] Started implementing jpgraph class library, first charts working --- src/chartgenerator.php | 179 +- src/classes/jpgraph/gd_image.inc.php | 1619 +++++ src/classes/jpgraph/jpg-config.inc.php | 227 + src/classes/jpgraph/jpgraph.php | 6391 +++++++++++++++++ src/classes/jpgraph/jpgraph_bar.php | 1009 +++ .../jpgraph/jpgraph_errhandler.inc.php | 278 + src/classes/jpgraph/jpgraph_gradient.php | 423 ++ src/classes/jpgraph/jpgraph_line.php | 632 ++ src/classes/jpgraph/jpgraph_pie.php | 1439 ++++ src/classes/jpgraph/jpgraph_pie3d.php | 923 +++ src/classes/jpgraph/jpgraph_plotband.php | 635 ++ src/classes/jpgraph/jpgraph_plotmark.inc.php | 497 ++ src/classes/jpgraph/jpgraph_ttf.inc.php | 339 + src/classes/jpgraph/lang/de.inc.php | 500 ++ src/classes/jpgraph/lang/en.inc.php | 500 ++ src/classes/jpgraph/lang/prod.inc.php | 357 + src/classes/logstream.class.php | 2 +- src/classes/logstreamdb.class.php | 2 +- src/classes/logstreamdisk.class.php | 5 +- src/classes/logstreampdo.class.php | 2 +- src/lang/de/main.php | 8 + src/lang/en/main.php | 2 + src/lang/pt_BR/main.php | 8 + src/templates/statistics.html | 16 +- 24 files changed, 15942 insertions(+), 51 deletions(-) create mode 100644 src/classes/jpgraph/gd_image.inc.php create mode 100644 src/classes/jpgraph/jpg-config.inc.php create mode 100644 src/classes/jpgraph/jpgraph.php create mode 100644 src/classes/jpgraph/jpgraph_bar.php create mode 100644 src/classes/jpgraph/jpgraph_errhandler.inc.php create mode 100644 src/classes/jpgraph/jpgraph_gradient.php create mode 100644 src/classes/jpgraph/jpgraph_line.php create mode 100644 src/classes/jpgraph/jpgraph_pie.php create mode 100644 src/classes/jpgraph/jpgraph_pie3d.php create mode 100644 src/classes/jpgraph/jpgraph_plotband.php create mode 100644 src/classes/jpgraph/jpgraph_plotmark.inc.php create mode 100644 src/classes/jpgraph/jpgraph_ttf.inc.php create mode 100644 src/classes/jpgraph/lang/de.inc.php create mode 100644 src/classes/jpgraph/lang/en.inc.php create mode 100644 src/classes/jpgraph/lang/prod.inc.php diff --git a/src/chartgenerator.php b/src/chartgenerator.php index dfe8d1a..804a244 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -88,6 +88,11 @@ else $content['error_occured'] = true; $content['error_details'] = $content['LN_GEN_ERROR_MISSINGCHARTFIELD']; } + +if ( isset($_GET['maxrecords']) ) + $content['maxrecords'] = intval($_GET['maxrecords']); +else + $content['maxrecords'] = 10; // --- // --- BEGIN CREATE TITLE @@ -96,26 +101,14 @@ $content['TITLE'] = InitPageTitle(); // --- BEGIN Custom Code -include_once ($gl_root_path . "classes/jpgraph/jpgraph.php"); -include_once ($gl_root_path . "classes/jpgraph/jpgraph_bar.php"); - -// Create Basic Image! -$myGraph = new Graph($content['chart_width'], $content['chart_width'], 'auto'); -$myGraph->SetScale("textlin"); -$myGraph->img->SetMargin(60,30,20,40); -$myGraph->yaxis->SetTitleMargin(45); -$myGraph->yaxis->scale->SetGrace(30); -$myGraph->SetShadow(); - -// Turn the tickmarks -$myGraph->xaxis->SetTickSide(SIDE_DOWN); -$myGraph->yaxis->SetTickSide(SIDE_LEFT); - // Get data and print on the image! if ( !$content['error_occured'] ) { if ( isset($content['Sources'][$currentSourceID]) ) { + // Include basic files needed + require_once ($gl_root_path . "classes/jpgraph/jpgraph.php"); + // Obtain and get the Config Object $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; @@ -126,39 +119,142 @@ if ( !$content['error_occured'] ) if ( $res == SUCCESS ) { // Obtain data from the logstream! - $chartData = $stream->GetCountSortedByField($content['chart_field'], $content['chart_fieldtype']); + $chartData = $stream->GetCountSortedByField($content['chart_field'], $content['chart_fieldtype'], $content['maxrecords']); // If data is valid, we have an array! if ( is_array($chartData) ) { - // Sort Array, so the highest count comes first! - array_multisort($chartData, SORT_NUMERIC, SORT_DESC); + // Create Y array! + foreach( $chartData as $myKey => $myData) + { +// echo $myKey . "
    "; + $YchartData[] = intval($myData); + $XchartData[] = strlen($myKey) > 0 ? $myKey : "Unknown"; + } - // Create y array! - foreach( $chartData as $myYData) - $YchartData[] = intval($myYData); + if ( $content['chart_type'] == CHART_CAKE ) + { + // Include additional code filers for this chart! + include_once ($gl_root_path . "classes/jpgraph/jpgraph_pie.php"); + include_once ($gl_root_path . "classes/jpgraph/jpgraph_pie3d.php"); - //print_r ($chartData); - //$datay=array(12,26,9,17,31); + // Create Basic Image, and set basic properties! + $graph = new PieGraph($content['chart_width'], $content['chart_width'], 'auto'); + $graph->SetMargin(60,20,30,30); // Adjust margin area + $graph->SetScale("textlin"); + $graph->SetMarginColor('white'); + $graph->SetBox(); // Box around plotarea - // Create a bar pot - $bplot = new BarPlot($YchartData); -// $bplot->SetFillColor("orange"); + // Set up the title for the graph +// $graph->title->Set('Messagecount sorted by "' . $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . '"'); +// $graph->title->SetFont(FF_VERDANA,FS_NORMAL,12); +// $graph->title->SetColor("darkred"); - // Use a shadow on the bar graphs (just use the default settings) - $bplot->SetShadow(); - $bplot->value->SetFormat("%2.0f",70); - $bplot->value->SetFont(FF_ARIAL,FS_NORMAL,9); - $bplot->value->SetColor("blue"); - $bplot->value->Show(); - - $myGraph->Add($bplot); - - $myGraph->title->Set("Chart blabla"); - $myGraph->xaxis->title->Set("X-title"); - $myGraph->yaxis->title->Set("Y-title"); + // Setup the tab title + $graph->tabtitle->Set('Messagecount sorted by "' . $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . '"'); + $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + // Setup font for axis + $graph->xaxis->SetFont(FF_VERDANA,FS_NORMAL,10); + $graph->yaxis->SetFont(FF_VERDANA,FS_NORMAL,10); + + // Show 0 label on Y-axis (default is not to show) + $graph->yscale->ticks->SupressZeroLabel(false); + + + // Create + $p1 = new PiePlot3D($YchartData); + $p1->SetLegends($XchartData); +// $targ=array("pie3d_csimex1.php?v=1","pie3d_csimex1.php?v=2","pie3d_csimex1.php?v=3", +// "pie3d_csimex1.php?v=4","pie3d_csimex1.php?v=5","pie3d_csimex1.php?v=6"); +// $alts=array("val=%d","val=%d","val=%d","val=%d","val=%d","val=%d"); +// $p1->SetCSIMTargets($targ,$alts); + + // Use absolute labels + $p1->SetLabelType(1); + $p1->value->SetFormat("%d"); + + // Move the pie slightly to the left + $p1->SetCenter(0.4,0.5); + + $graph->Add($p1); + } + else if ( $content['chart_type'] == CHART_BARS_VERTICAL ) + { + // Include additional code filers for this chart! + include_once ($gl_root_path . "classes/jpgraph/jpgraph_bar.php"); + include_once ($gl_root_path . "classes/jpgraph/jpgraph_line.php"); + + // Create Basic Image, and set basic properties! + $graph = new Graph($content['chart_width'], $content['chart_width'], 'auto'); + $graph->SetMargin(60,20,30,30); // Adjust margin area + $graph->SetScale("textlin"); + $graph->SetMarginColor('white'); + $graph->SetBox(); // Box around plotarea + + // Set up the title for the graph + // $graph->title->Set("Bar gradient (Left reflection)"); + // $graph->title->SetFont(FF_VERDANA,FS_NORMAL,12); + // $graph->title->SetColor("darkred"); + + // Setup font for axis + $graph->xaxis->SetFont(FF_VERDANA,FS_NORMAL,10); + $graph->yaxis->SetFont(FF_VERDANA,FS_NORMAL,10); + + // Show 0 label on Y-axis (default is not to show) + $graph->yscale->ticks->SupressZeroLabel(false); + + + // Setup the tab title + $graph->tabtitle->Set('Messagecount sorted by "' . $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . '"'); + $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + + // Setup the X and Y grid + $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); + $graph->ygrid->SetLineStyle('dashed'); + $graph->ygrid->SetColor('gray'); + $graph->xgrid->Show(); + $graph->xgrid->SetLineStyle('dashed'); + $graph->xgrid->SetColor('gray'); + + // Setup X-axis labels + $graph->xaxis->SetTickLabels($XchartData); + $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->xaxis->SetLabelAngle(0); + + // Create a bar pot + $bplot = new BarPlot($YchartData); + $bplot->SetWidth(0.6); + $fcol='#440000'; + $tcol='#FF9090'; + $bplot->SetFillGradient($fcol,$tcol,GRAD_LEFT_REFLECTION); + $graph->Add($bplot); + + // Create filled line plot + $lplot = new LinePlot($YchartData); + $lplot->SetFillColor('skyblue@0.5'); + $lplot->SetColor('navy@0.7'); + $lplot->SetBarCenter(); + + $lplot->mark->SetType(MARK_SQUARE); + $lplot->mark->SetColor('blue@0.5'); + $lplot->mark->SetFillColor('lightblue'); + $lplot->mark->SetSize(6); + + $graph->Add($lplot); + } + else if ( $content['chart_type'] == CHART_BARS_HORIZONTAL ) + { + $content['error_occured'] = true; + $content['error_details'] = $content['LN_GEN_ERROR_INVALIDTYPE']; + + } + else + { + $content['error_occured'] = true; + $content['error_details'] = $content['LN_GEN_ERROR_INVALIDTYPE']; + } } else { @@ -207,6 +303,7 @@ if ( $content['error_occured'] ) imagerectangle( $myImage, 0, 0, $content['chart_width']-1, $content['chart_width']-1, $gray ); imagefill( $myImage, 1, 1, $white ); */ + $text_color = imagecolorallocate($myImage, 255, 0, 0); imagestring($myImage, 3, 10, 10, $content['LN_GEN_ERRORDETAILS'], $text_color); imagestring($myImage, 3, 10, 25, $content['error_details'], $text_color); @@ -219,11 +316,9 @@ if ( $content['error_occured'] ) } // --- - // --- Output the image - -// Send back the HTML page which will call this script again to retrieve the image. -$myGraph->StrokeCSIM(); +$graph->Stroke(); +//$graph->StrokeCSIM(); // --- ?> \ No newline at end of file diff --git a/src/classes/jpgraph/gd_image.inc.php b/src/classes/jpgraph/gd_image.inc.php new file mode 100644 index 0000000..f8b4b01 --- /dev/null +++ b/src/classes/jpgraph/gd_image.inc.php @@ -0,0 +1,1619 @@ +CreateImgCanvas($aWidth,$aHeight); + if( $aSetAutoMargin ) + $this->SetAutoMargin(); + + if( !$this->SetImgFormat($aFormat) ) { + JpGraphError::RaiseL(25081,$aFormat);//("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]"); + } + $this->ttf = new TTF(); + $this->langconv = new LanguageConv(); + } + + // Enable interlacing in images + function SetInterlace($aFlg=true) { + $this->iInterlace=$aFlg; + } + + // Should we use anti-aliasing. Note: This really slows down graphics! + function SetAntiAliasing($aFlg=true) { + $this->use_anti_aliasing = $aFlg; + if( function_exists('imageantialias') ) { + imageantialias($this->img,$aFlg); + } + else { + JpGraphError::RaiseL(25128);//('The function imageantialias() is not available in your PHP installation. Use the GD version that comes with PHP and not the standalone version.') + } + } + + function CreateRawCanvas($aWidth=0,$aHeight=0) { + if( $aWidth <= 1 || $aHeight <= 1 ) { + JpGraphError::RaiseL(25082,$aWidth,$aHeight);//("Illegal sizes specified for width or height when creating an image, (width=$aWidth, height=$aHeight)"); + } + + if( USE_TRUECOLOR ) { + $this->img = @imagecreatetruecolor($aWidth, $aHeight); + if( $this->img < 1 ) { + JpGraphError::RaiseL(25126); + //die("Can't create truecolor image. Check that you really have GD2 library installed."); + } + $this->SetAlphaBlending(); + } else { + $this->img = @imagecreate($aWidth, $aHeight); + if( $this->img < 1 ) { + JpGraphError::RaiseL(25126); + //die("JpGraph Error: Can't create image. Check that you really have the GD library installed."); + } + } + + if( $this->iInterlace ) { + imageinterlace($this->img,1); + } + if( $this->rgb != null ) + $this->rgb->img = $this->img ; + else + $this->rgb = new RGB($this->img); + } + + function CloneCanvasH() { + $oldimage = $this->img; + $this->CreateRawCanvas($this->width,$this->height); + imagecopy($this->img,$oldimage,0,0,0,0,$this->width,$this->height); + return $oldimage; + } + + function CreateImgCanvas($aWidth=0,$aHeight=0) { + + $old = array($this->img,$this->width,$this->height); + + $aWidth = round($aWidth); + $aHeight = round($aHeight); + + $this->width=$aWidth; + $this->height=$aHeight; + + + if( $aWidth==0 || $aHeight==0 ) { + // We will set the final size later. + // Note: The size must be specified before any other + // img routines that stroke anything are called. + $this->img = null; + $this->rgb = null; + return $old; + } + + $this->CreateRawCanvas($aWidth,$aHeight); + // Set canvas color (will also be the background color for a + // a pallett image + $this->SetColor($this->canvascolor); + $this->FilledRectangle(0,0,$aWidth,$aHeight); + + return $old ; + } + + function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) { + if( $aw === -1 ) { + $aw = $aWidth; + $ah = $aHeight; + $f = 'imagecopyresized'; + } + else { + $f = 'imagecopyresampled'; + } + $f($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah); + } + + function Copy($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1) { + $this->CopyCanvasH($this->img,$fromImg,$toX,$toY,$fromX,$fromY, + $toWidth,$toHeight,$fromWidth,$fromHeight); + } + + function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) { + if( $aMix == 100 ) { + $this->CopyCanvasH($this->img,$fromImg, + $toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight); + } + else { + if( ($fromWidth != -1 && ($fromWidth != $toWidth)) || + ($fromHeight != -1 && ($fromHeight != $fromHeight)) ) { + // Create a new canvas that will hold the re-scaled original from image + if( $toWidth <= 1 || $toHeight <= 1 ) { + JpGraphError::RaiseL(25083);//('Illegal image size when copying image. Size for copied to image is 1 pixel or less.'); + } + if( USE_TRUECOLOR ) { + $tmpimg = @imagecreatetruecolor($toWidth, $toHeight); + } else { + $tmpimg = @imagecreate($toWidth, $toHeight); + } + if( $tmpimg < 1 ) { + JpGraphError::RaiseL(25084);//('Failed to create temporary GD canvas. Out of memory ?'); + } + $this->CopyCanvasH($tmpimg,$fromImg,0,0,0,0, + $toWidth,$toHeight,$fromWidth,$fromHeight); + $fromImg = $tmpimg; + } + imagecopymerge($this->img,$fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$aMix); + } + } + + static function GetWidth($aImg=null) { + if( $aImg === null ) + $aImg = $this->img; + return imagesx($aImg); + } + + static function GetHeight($aImg=null) { + if( $aImg === null ) + $aImg = $this->img; + return imagesy($aImg); + } + + static function CreateFromString($aStr) { + $img = imagecreatefromstring($aStr); + if( $img === false ) { + JpGraphError::RaiseL(25085);//('An image can not be created from the supplied string. It is either in a format not supported or the string is representing an corrupt image.'); + } + return $img; + } + + function SetCanvasH($aHdl) { + $this->img = $aHdl; + $this->rgb->img = $aHdl; + } + + function SetCanvasColor($aColor) { + $this->canvascolor = $aColor ; + } + + function SetAlphaBlending($aFlg=true) { + ImageAlphaBlending($this->img,$aFlg); + } + + + function SetAutoMargin() { + GLOBAL $gJpgBrandTiming; + $min_bm=5; + /* + if( $gJpgBrandTiming ) + $min_bm=15; + */ + $lm = min(40,$this->width/7); + $rm = min(20,$this->width/10); + $tm = max(5,$this->height/7); + $bm = max($min_bm,$this->height/7); + $this->SetMargin($lm,$rm,$tm,$bm); + } + + + //--------------- + // PUBLIC METHODS + + function SetFont($family,$style=FS_NORMAL,$size=10) { + $this->font_family=$family; + $this->font_style=$style; + $this->font_size=$size; + $this->font_file=''; + if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){ + ++$this->font_family; + } + if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file + + // Check that this PHP has support for TTF fonts + if( !function_exists('imagettfbbox') ) { + JpGraphError::RaiseL(25087);//('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.'); + } + $this->font_file = $this->ttf->File($this->font_family,$this->font_style); + } + } + + // Get the specific height for a text string + function GetTextHeight($txt="",$angle=0) { + $tmp = split("\n",$txt); + $n = count($tmp); + $m=0; + for($i=0; $i< $n; ++$i) + $m = max($m,strlen($tmp[$i])); + + if( $this->font_family <= FF_FONT2+1 ) { + if( $angle==0 ) { + $h = imagefontheight($this->font_family); + if( $h === false ) { + JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.'); + } + + return $n*$h; + } + else { + $w = @imagefontwidth($this->font_family); + if( $w === false ) { + JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.'); + } + + return $m*$w; + } + } + else { + $bbox = $this->GetTTFBBox($txt,$angle); + return $bbox[1]-$bbox[5]; + } + } + + // Estimate font height + function GetFontHeight($angle=0) { + $txt = "XOMg"; + return $this->GetTextHeight($txt,$angle); + } + + // Approximate font width with width of letter "O" + function GetFontWidth($angle=0) { + $txt = 'O'; + return $this->GetTextWidth($txt,$angle); + } + + // Get actual width of text in absolute pixels + function GetTextWidth($txt,$angle=0) { + + $tmp = split("\n",$txt); + $n = count($tmp); + if( $this->font_family <= FF_FONT2+1 ) { + + $m=0; + for($i=0; $i < $n; ++$i) { + $l=strlen($tmp[$i]); + if( $l > $m ) { + $m = $l; + } + } + + if( $angle==0 ) { + $w = @imagefontwidth($this->font_family); + if( $w === false ) { + JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.'); + } + return $m*$w; + } + else { + // 90 degrees internal so height becomes width + $h = @imagefontheight($this->font_family); + if( $h === false ) { + JpGraphError::RaiseL(25089);//('You have a misconfigured GD font support. The call to imagefontheight() fails.'); + } + return $n*$h; + } + } + else { + // For TTF fonts we must walk through a lines and find the + // widest one which we use as the width of the multi-line + // paragraph + $m=0; + for( $i=0; $i < $n; ++$i ) { + $bbox = $this->GetTTFBBox($tmp[$i],$angle); + $mm = $bbox[2] - $bbox[0]; + if( $mm > $m ) + $m = $mm; + } + return $m; + } + } + + // Draw text with a box around it + function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black", + $shadowcolor=false,$paragraph_align="left", + $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) { + + if( !is_numeric($dir) ) { + if( $dir=="h" ) $dir=0; + elseif( $dir=="v" ) $dir=90; + else JpGraphError::RaiseL(25090,$dir);//(" Unknown direction specified in call to StrokeBoxedText() [$dir]"); + } + + if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) { + $width=$this->GetTextWidth($txt,$dir) ; + $height=$this->GetTextHeight($txt,$dir) ; + } + else { + $width=$this->GetBBoxWidth($txt,$dir) ; + $height=$this->GetBBoxHeight($txt,$dir) ; + } + + $height += 2*$ymarg; + $width += 2*$xmarg; + + if( $this->text_halign=="right" ) $x -= $width; + elseif( $this->text_halign=="center" ) $x -= $width/2; + if( $this->text_valign=="bottom" ) $y -= $height; + elseif( $this->text_valign=="center" ) $y -= $height/2; + + $olda = $this->SetAngle(0); + + if( $shadowcolor ) { + $this->PushColor($shadowcolor); + $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth, + $x+$width+$dropwidth,$y+$height-$ymarg+$dropwidth, + $cornerradius); + $this->PopColor(); + $this->PushColor($fcolor); + $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg, + $x+$width,$y+$height-$ymarg, + $cornerradius); + $this->PopColor(); + $this->PushColor($bcolor); + $this->RoundedRectangle($x-$xmarg,$y-$ymarg, + $x+$width,$y+$height-$ymarg,$cornerradius); + $this->PopColor(); + } + else { + if( $fcolor ) { + $oc=$this->current_color; + $this->SetColor($fcolor); + $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius); + $this->current_color=$oc; + } + if( $bcolor ) { + $oc=$this->current_color; + $this->SetColor($bcolor); + $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius); + $this->current_color=$oc; + } + } + + $h=$this->text_halign; + $v=$this->text_valign; + $this->SetTextAlign("left","top"); + $this->StrokeText($x, $y, $txt, $dir, $paragraph_align); + $bb = array($x-$xmarg,$y+$height-$ymarg,$x+$width,$y+$height-$ymarg, + $x+$width,$y-$ymarg,$x-$xmarg,$y-$ymarg); + $this->SetTextAlign($h,$v); + + $this->SetAngle($olda); + + return $bb; + } + + // Set text alignment + function SetTextAlign($halign,$valign="bottom") { + $this->text_halign=$halign; + $this->text_valign=$valign; + } + + + function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$aDebug=false) { + + if( is_numeric($dir) && $dir!=90 && $dir!=0) + JpGraphError::RaiseL(25091);//(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead."); + + $h=$this->GetTextHeight($txt); + $fh=$this->GetFontHeight(); + $w=$this->GetTextWidth($txt); + + if( $this->text_halign=="right") + $x -= $dir==0 ? $w : $h; + elseif( $this->text_halign=="center" ) { + // For center we subtract 1 pixel since this makes the middle + // be prefectly in the middle + $x -= $dir==0 ? $w/2-1 : $h/2; + } + if( $this->text_valign=="top" ) + $y += $dir==0 ? $h : $w; + elseif( $this->text_valign=="center" ) + $y += $dir==0 ? $h/2 : $w/2; + + if( $dir==90 ) { + imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color); + $aBoundingBox = array(round($x),round($y),round($x),round($y-$w),round($x+$h),round($y-$w),round($x+$h),round($y)); + if( $aDebug ) { + // Draw bounding box + $this->PushColor('green'); + $this->Polygon($aBoundingBox,true); + $this->PopColor(); + } + } + else { + if( ereg("\n",$txt) ) { + $tmp = split("\n",$txt); + for($i=0; $i < count($tmp); ++$i) { + $w1 = $this->GetTextWidth($tmp[$i]); + if( $paragraph_align=="left" ) { + imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color); + } + elseif( $paragraph_align=="right" ) { + imagestring($this->img,$this->font_family,$x+($w-$w1), + $y-$h+1+$i*$fh,$tmp[$i],$this->current_color); + } + else { + imagestring($this->img,$this->font_family,$x+$w/2-$w1/2, + $y-$h+1+$i*$fh,$tmp[$i],$this->current_color); + } + } + } + else { + //Put the text + imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color); + } + if( $aDebug ) { + // Draw the bounding rectangle and the bounding box + $p1 = array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y)); + + // Draw bounding box + $this->PushColor('green'); + $this->Polygon($p1,true); + $this->PopColor(); + + } + $aBoundingBox=array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y)); + } + } + + function AddTxtCR($aTxt) { + // If the user has just specified a '\n' + // instead of '\n\t' we have to add '\r' since + // the width will be too muchy otherwise since when + // we print we stroke the individually lines by hand. + $e = explode("\n",$aTxt); + $n = count($e); + for($i=0; $i<$n; ++$i) { + $e[$i]=str_replace("\r","",$e[$i]); + } + return implode("\n\r",$e); + } + + function GetTTFBBox($aTxt,$aAngle=0) { + $bbox = @ImageTTFBBox($this->font_size,$aAngle,$this->font_file,$aTxt); + if( $bbox === false ) { + JpGraphError::RaiseL(25092,$this->font_file); +//("There is either a configuration problem with TrueType or a problem reading font file (".$this->font_file."). Make sure file exists and is in a readable place for the HTTP process. (If 'basedir' restriction is enabled in PHP then the font file must be located in the document root.). It might also be a wrongly installed FreeType library. Try uppgrading to at least FreeType 2.1.13 and recompile GD with the correct setup so it can find the new FT library."); + } + return $bbox; + } + + function GetBBoxTTF($aTxt,$aAngle=0) { + // Normalize the bounding box to become a minimum + // enscribing rectangle + + $aTxt = $this->AddTxtCR($aTxt); + + if( !is_readable($this->font_file) ) { + JpGraphError::RaiseL(25093,$this->font_file); +//('Can not read font file ('.$this->font_file.') in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.'); + } + $bbox = $this->GetTTFBBox($aTxt,$aAngle); + + if( $aAngle==0 ) + return $bbox; + if( $aAngle >= 0 ) { + if( $aAngle <= 90 ) { //<=0 + $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1], + $bbox[2],$bbox[5],$bbox[6],$bbox[5]); + } + elseif( $aAngle <= 180 ) { //<= 2 + $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7], + $bbox[0],$bbox[3],$bbox[4],$bbox[3]); + } + elseif( $aAngle <= 270 ) { //<= 3 + $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5], + $bbox[6],$bbox[1],$bbox[2],$bbox[1]); + } + else { + $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3], + $bbox[4],$bbox[7],$bbox[0],$bbox[7]); + } + } + elseif( $aAngle < 0 ) { + if( $aAngle <= -270 ) { // <= -3 + $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1], + $bbox[2],$bbox[5],$bbox[6],$bbox[5]); + } + elseif( $aAngle <= -180 ) { // <= -2 + $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3], + $bbox[4],$bbox[7],$bbox[0],$bbox[7]); + } + elseif( $aAngle <= -90 ) { // <= -1 + $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5], + $bbox[6],$bbox[1],$bbox[2],$bbox[1]); + } + else { + $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3], + $bbox[4],$bbox[7],$bbox[0],$bbox[7]); + } + } + return $bbox; + } + + function GetBBoxHeight($aTxt,$aAngle=0) { + $box = $this->GetBBoxTTF($aTxt,$aAngle); + return $box[1]-$box[7]+1; + } + + function GetBBoxWidth($aTxt,$aAngle=0) { + $box = $this->GetBBoxTTF($aTxt,$aAngle); + return $box[2]-$box[0]+1; + } + + function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$debug=false) { + + // Setupo default inter line margin for paragraphs to + // 25% of the font height. + $ConstLineSpacing = 0.25 ; + + // Remember the anchor point before adjustment + if( $debug ) { + $ox=$x; + $oy=$y; + } + + if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) { + // Format a single line + + $txt = $this->AddTxtCR($txt); + + $bbox=$this->GetBBoxTTF($txt,$dir); + + // Align x,y ot lower left corner of bbox + $x -= $bbox[0]; + $y -= $bbox[1]; + + // Note to self: "topanchor" is deprecated after we changed the + // bopunding box stuff. + if( $this->text_halign=="right" || $this->text_halign=="topanchor" ) + $x -= $bbox[2]-$bbox[0]; + elseif( $this->text_halign=="center" ) $x -= ($bbox[2]-$bbox[0])/2; + + if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1]; + elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2; + + ImageTTFText ($this->img, $this->font_size, $dir, $x, $y, + $this->current_color,$this->font_file,$txt); + + // Calculate and return the co-ordinates for the bounding box + $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt); + $p1 = array(); + + + for($i=0; $i < 4; ++$i) { + $p1[] = round($box[$i*2]+$x); + $p1[] = round($box[$i*2+1]+$y); + } + $aBoundingBox = $p1; + + // Debugging code to highlight the bonding box and bounding rectangle + // For text at 0 degrees the bounding box and bounding rectangle are the + // same + if( $debug ) { + // Draw the bounding rectangle and the bounding box + $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt); + $p = array(); + $p1 = array(); + for($i=0; $i < 4; ++$i) { + $p[] = $bbox[$i*2]+$x; + $p[] = $bbox[$i*2+1]+$y; + $p1[] = $box[$i*2]+$x; + $p1[] = $box[$i*2+1]+$y; + } + + // Draw bounding box + $this->PushColor('green'); + $this->Polygon($p1,true); + $this->PopColor(); + + // Draw bounding rectangle + $this->PushColor('darkgreen'); + $this->Polygon($p,true); + $this->PopColor(); + + // Draw a cross at the anchor point + $this->PushColor('red'); + $this->Line($ox-15,$oy,$ox+15,$oy); + $this->Line($ox,$oy-15,$ox,$oy+15); + $this->PopColor(); + } + } + else { + // Format a text paragraph + $fh=$this->GetFontHeight(); + + // Line margin is 25% of font height + $linemargin=round($fh*$ConstLineSpacing); + $fh += $linemargin; + $w=$this->GetTextWidth($txt); + + $y -= $linemargin/2; + $tmp = split("\n",$txt); + $nl = count($tmp); + $h = $nl * $fh; + + if( $this->text_halign=="right") + $x -= $dir==0 ? $w : $h; + elseif( $this->text_halign=="center" ) { + $x -= $dir==0 ? $w/2 : $h/2; + } + + if( $this->text_valign=="top" ) + $y += $dir==0 ? $h : $w; + elseif( $this->text_valign=="center" ) + $y += $dir==0 ? $h/2 : $w/2; + + // Here comes a tricky bit. + // Since we have to give the position for the string at the + // baseline this means thaht text will move slightly up + // and down depending on any of it's character descend below + // the baseline, for example a 'g'. To adjust the Y-position + // we therefore adjust the text with the baseline Y-offset + // as used for the current font and size. This will keep the + // baseline at a fixed positoned disregarding the actual + // characters in the string. + $standardbox = $this->GetTTFBBox('Gg',$dir); + $yadj = $standardbox[1]; + $xadj = $standardbox[0]; + $aBoundingBox = array(); + for($i=0; $i < $nl; ++$i) { + $wl = $this->GetTextWidth($tmp[$i]); + $bbox = $this->GetTTFBBox($tmp[$i],$dir); + if( $paragraph_align=="left" ) { + $xl = $x; + } + elseif( $paragraph_align=="right" ) { + $xl = $x + ($w-$wl); + } + else { + // Center + $xl = $x + $w/2 - $wl/2 ; + } + + $xl -= $bbox[0]; + $yl = $y - $yadj; + $xl = $xl - $xadj; + ImageTTFText ($this->img, $this->font_size, $dir, + $xl, $yl-($h-$fh)+$fh*$i, + $this->current_color,$this->font_file,$tmp[$i]); + + if( $debug ) { + // Draw the bounding rectangle around each line + $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]); + $p = array(); + for($j=0; $j < 4; ++$j) { + $p[] = $bbox[$j*2]+$xl; + $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i; + } + + // Draw bounding rectangle + $this->PushColor('darkgreen'); + $this->Polygon($p,true); + $this->PopColor(); + } + } + + // Get the bounding box + $bbox = $this->GetBBoxTTF($txt,$dir); + for($j=0; $j < 4; ++$j) { + $bbox[$j*2]+= round($x); + $bbox[$j*2+1]+= round($y - ($h-$fh) - $yadj); + } + $aBoundingBox = $bbox; + + if( $debug ) { + // Draw a cross at the anchor point + $this->PushColor('red'); + $this->Line($ox-25,$oy,$ox+25,$oy); + $this->Line($ox,$oy-25,$ox,$oy+25); + $this->PopColor(); + } + + } + } + + function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) { + + $x = round($x); + $y = round($y); + + // Do special language encoding + $txt = $this->langconv->Convert($txt,$this->font_family); + + if( !is_numeric($dir) ) + JpGraphError::RaiseL(25094);//(" Direction for text most be given as an angle between 0 and 90."); + + if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) { + $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug); + } + elseif($this->font_family >= _FIRST_FONT && $this->font_family <= _LAST_FONT) { + $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug); + } + else + JpGraphError::RaiseL(25095);//(" Unknown font font family specification. "); + return $boundingbox; + } + + function SetMargin($lm,$rm,$tm,$bm) { + $this->left_margin=$lm; + $this->right_margin=$rm; + $this->top_margin=$tm; + $this->bottom_margin=$bm; + $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ; + $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ; + if( $this->width > 0 && $this->height > 0 ) { + if( $this->plotwidth < 0 || $this->plotheight < 0 ) + JpGraphError::raise("To small plot area. ($lm,$rm,$tm,$bm : $this->plotwidth x $this->plotheight). With the given image size and margins there is to little space left for the plot. Increase the plot size or reduce the margins."); + } + } + + function SetTransparent($color) { + imagecolortransparent ($this->img,$this->rgb->allocate($color)); + } + + function SetColor($color,$aAlpha=0) { + $this->current_color_name = $color; + $this->current_color=$this->rgb->allocate($color,$aAlpha); + if( $this->current_color == -1 ) { + $tc=imagecolorstotal($this->img); + JpGraphError::RaiseL(25096); +//("Can't allocate any more colors. Image has already allocated maximum of $tc colors. This might happen if you have anti-aliasing turned on together with a background image or perhaps gradient fill since this requires many, many colors. Try to turn off anti-aliasing. If there is still a problem try downgrading the quality of the background image to use a smaller pallete to leave some entries for your graphs. You should try to limit the number of colors in your background image to 64. If there is still problem set the constant DEFINE(\"USE_APPROX_COLORS\",true); in jpgraph.php This will use approximative colors when the palette is full. Unfortunately there is not much JpGraph can do about this since the palette size is a limitation of current graphic format and what the underlying GD library suppports."); + } + return $this->current_color; + } + + function PushColor($color) { + if( $color != "" ) { + $this->colorstack[$this->colorstackidx]=$this->current_color_name; + $this->colorstack[$this->colorstackidx+1]=$this->current_color; + $this->colorstackidx+=2; + $this->SetColor($color); + } + else { + JpGraphError::RaiseL(25097);//("Color specified as empty string in PushColor()."); + } + } + + function PopColor() { + if($this->colorstackidx<1) + JpGraphError::RaiseL(25098);//(" Negative Color stack index. Unmatched call to PopColor()"); + $this->current_color=$this->colorstack[--$this->colorstackidx]; + $this->current_color_name=$this->colorstack[--$this->colorstackidx]; + } + + + function SetLineWeight($weight) { + imagesetthickness($this->img,$weight); + $this->line_weight = $weight; + } + + function SetStartPoint($x,$y) { + $this->lastx=round($x); + $this->lasty=round($y); + } + + function Arc($cx,$cy,$w,$h,$s,$e) { + // GD Arc doesn't like negative angles + while( $s < 0) $s += 360; + while( $e < 0) $e += 360; + + imagearc($this->img,round($cx),round($cy),round($w),round($h), + $s,$e,$this->current_color); + } + + function FilledArc($xc,$yc,$w,$h,$s,$e,$style='') { + while( $s < 0 ) $s += 360; + while( $e < 0 ) $e += 360; + if( $style=='' ) + $style=IMG_ARC_PIE; + if( abs($s-$e) > 0.001 ) { + imagefilledarc($this->img,round($xc),round($yc),round($w),round($h), + round($s),round($e),$this->current_color,$style); + } + } + + function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) { + $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name); + } + + function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") { + $s = round($s); $e = round($e); + $w = round($w); $h = round($h); + $xc = round($xc); $yc = round($yc); + if( $s ==$e ) { + // A full circle. We draw this a plain circle + $this->PushColor($fillcolor); + imagefilledellipse($this->img,$xc,$yc,2*$w,2*$h,$this->current_color); + $this->PopColor(); + $this->PushColor($arccolor); + imageellipse($this->img,$xc,$yc,2*$w,2*$h,$this->current_color); + $this->Line($xc,$yc,cos($s*M_PI/180)*$w+$xc,$yc+sin($s*M_PI/180)*$h); + $this->PopColor(); + } + else { + $this->PushColor($fillcolor); + $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e); + $this->PopColor(); + if( $arccolor != "" ) { + $this->PushColor($arccolor); + // We add 2 pixels to make the Arc() better aligned with + // the filled arc. + imagefilledarc($this->img,$xc,$yc,2*$w,2*$h,$s,$e,$this->current_color,IMG_ARC_NOFILL | IMG_ARC_EDGED ) ; + $this->PopColor(); + } + } + } + + function Ellipse($xc,$yc,$w,$h) { + $this->Arc($xc,$yc,$w,$h,0,360); + } + + function Circle($xc,$yc,$r) { + imageellipse($this->img,round($xc),round($yc),$r*2,$r*2,$this->current_color); + } + + function FilledCircle($xc,$yc,$r) { + imagefilledellipse($this->img,round($xc),round($yc),2*$r,2*$r,$this->current_color); + } + + // Linear Color InterPolation + function lip($f,$t,$p) { + $p = round($p,1); + $r = $f[0] + ($t[0]-$f[0])*$p; + $g = $f[1] + ($t[1]-$f[1])*$p; + $b = $f[2] + ($t[2]-$f[2])*$p; + return array($r,$g,$b); + } + + // Set line style dashed, dotted etc + function SetLineStyle($s) { + if( is_numeric($s) ) { + if( $s<1 || $s>4 ) + JpGraphError::RaiseL(25101,$s);//(" Illegal numeric argument to SetLineStyle(): ($s)"); + } + elseif( is_string($s) ) { + if( $s == "solid" ) $s=1; + elseif( $s == "dotted" ) $s=2; + elseif( $s == "dashed" ) $s=3; + elseif( $s == "longdashed" ) $s=4; + else JpGraphError::RaiseL(25102,$s);//(" Illegal string argument to SetLineStyle(): $s"); + } + else { + JpGraphError::RaiseL(25103,$s);//(" Illegal argument to SetLineStyle $s"); + } + $old = $this->line_style; + $this->line_style=$s; + return $old; + } + + // Same as Line but take the line_style into account + function StyleLine($x1,$y1,$x2,$y2,$aStyle='') { + if( $this->line_weight <= 0 ) + return; + + if( $aStyle === '' ) { + $aStyle = $this->line_style; + } + + // Add error check since dashed line will only work if anti-alias is disabled + // this is a limitation in GD + + switch( $aStyle ) { + case 1:// Solid + $this->Line($x1,$y1,$x2,$y2); + break; + case 2: // Dotted + $this->DashedLine($x1,$y1,$x2,$y2,2,6); + break; + case 3: // Dashed + $this->DashedLine($x1,$y1,$x2,$y2,5,9); + break; + case 4: // Longdashes + $this->DashedLine($x1,$y1,$x2,$y2,9,13); + break; + default: + JpGraphError::RaiseL(25104,$this->line_style);//(" Unknown line style: $this->line_style "); + break; + } + } + + function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) { + + if( $this->line_weight <= 0 ) + return; + + // Add error check to make sure anti-alias is not enabled. + // Dashed line does not work with anti-alias enabled. This + // is a limitation in GD. + if( $this->use_anti_aliasing ) { + JpGraphError::RaiseL(25129); // Anti-alias can not be used with dashed lines. Please disable anti-alias or use solid lines. + } + + + $x1 = round($x1); + $x2 = round($x2); + $y1 = round($y1); + $y2 = round($y2); + + $style = array_fill(0,$dash_length,$this->current_color); + $style = array_pad($style,$dash_space,IMG_COLOR_TRANSPARENT); + imagesetstyle($this->img, $style); + imageline($this->img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); + $this->lastx=$x2; $this->lasty=$y2; + } + + function Line($x1,$y1,$x2,$y2) { + + if( $this->line_weight <= 0 ) + return; + + $x1 = round($x1); + $x2 = round($x2); + $y1 = round($y1); + $y2 = round($y2); + + imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color); + $this->lastx=$x2; $this->lasty=$y2; + } + + function Polygon($p,$closed=FALSE,$fast=FALSE) { + + if( $this->line_weight <= 0 ) + return; + + $n=count($p); + $oldx = $p[0]; + $oldy = $p[1]; + if( $fast ) { + for( $i=2; $i < $n; $i+=2 ) { + imageline($this->img,$oldx,$oldy,$p[$i],$p[$i+1],$this->current_color); + $oldx = $p[$i]; + $oldy = $p[$i+1]; + } + if( $closed ) { + imageline($this->img,$p[$n*2-2],$p[$n*2-1],$p[0],$p[1],$this->current_color); + } + } + else { + for( $i=2; $i < $n; $i+=2 ) { + $this->StyleLine($oldx,$oldy,$p[$i],$p[$i+1]); + $oldx = $p[$i]; + $oldy = $p[$i+1]; + } + if( $closed ) + $this->StyleLine($oldx,$oldy,$p[0],$p[1]); + } + } + + function FilledPolygon($pts) { + $n=count($pts); + if( $n == 0 ) { + JpGraphError::RaiseL(25105);//('NULL data specified for a filled polygon. Check that your data is not NULL.'); + } + for($i=0; $i < $n; ++$i) + $pts[$i] = round($pts[$i]); + imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color); + } + + function Rectangle($xl,$yu,$xr,$yl) { + $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu)); + } + + function FilledRectangle($xl,$yu,$xr,$yl) { + $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl)); + } + + function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) { + // Fill a rectangle with lines of two colors + if( $style===1 ) { + // Horizontal stripe + if( $yl < $yu ) { + $t = $yl; $yl=$yu; $yu=$t; + } + for( $y=$yu; $y <= $yl; ++$y) { + $this->SetColor($color1); + $this->Line($xl,$y,$xr,$y); + ++$y; + $this->SetColor($color2); + $this->Line($xl,$y,$xr,$y); + } + } + else { + if( $xl < $xl ) { + $t = $xl; $xl=$xr; $xr=$t; + } + for( $x=$xl; $x <= $xr; ++$x) { + $this->SetColor($color1); + $this->Line($x,$yu,$x,$yl); + ++$x; + $this->SetColor($color2); + $this->Line($x,$yu,$x,$yl); + } + } + } + + function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) { + // This is complicated by the fact that we must also handle the case where + // the reactangle has no fill color + $this->PushColor($shadow_color); + $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl-$shadow_width-1); + $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl); + //$this->FilledRectangle($xl+$shadow_width,$yu+$shadow_width,$xr,$yl); + $this->PopColor(); + if( $fcolor==false ) + $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1); + else { + $this->PushColor($fcolor); + $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1); + $this->PopColor(); + $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1); + } + } + + function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) { + if( $r==0 ) { + $this->FilledRectangle($xt,$yt,$xr,$yl); + return; + } + + // To avoid overlapping fillings (which will look strange + // when alphablending is enabled) we have no choice but + // to fill the five distinct areas one by one. + + // Center square + $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r); + // Top band + $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1); + // Bottom band + $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl); + // Left band + $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r); + // Right band + $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r); + + // Topleft & Topright arc + $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270); + $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360); + + // Bottomleft & Bottom right arc + $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180); + $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90); + + } + + function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) { + + if( $r==0 ) { + $this->Rectangle($xt,$yt,$xr,$yl); + return; + } + + // Top & Bottom line + $this->Line($xt+$r,$yt,$xr-$r,$yt); + $this->Line($xt+$r,$yl,$xr-$r,$yl); + + // Left & Right line + $this->Line($xt,$yt+$r,$xt,$yl-$r); + $this->Line($xr,$yt+$r,$xr,$yl-$r); + + // Topleft & Topright arc + $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270); + $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360); + + // Bottomleft & Bottomright arc + $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180); + $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90); + } + + function FilledBevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='darkgray@0.4') { + $this->FilledRectangle($x1,$y1,$x2,$y2); + $this->Bevel($x1,$y1,$x2,$y2,$depth,$color1,$color2); + } + + function Bevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='black@0.5') { + $this->PushColor($color1); + for( $i=0; $i < $depth; ++$i ) { + $this->Line($x1+$i,$y1+$i,$x1+$i,$y2-$i); + $this->Line($x1+$i,$y1+$i,$x2-$i,$y1+$i); + } + $this->PopColor(); + + $this->PushColor($color2); + for( $i=0; $i < $depth; ++$i ) { + $this->Line($x1+$i,$y2-$i,$x2-$i,$y2-$i); + $this->Line($x2-$i,$y1+$i,$x2-$i,$y2-$i-1); + } + $this->PopColor(); + } + + function StyleLineTo($x,$y) { + $this->StyleLine($this->lastx,$this->lasty,$x,$y); + $this->lastx=$x; + $this->lasty=$y; + } + + function LineTo($x,$y) { + $this->Line($this->lastx,$this->lasty,$x,$y); + $this->lastx=$x; + $this->lasty=$y; + } + + function Point($x,$y) { + imagesetpixel($this->img,round($x),round($y),$this->current_color); + } + + function Fill($x,$y) { + imagefill($this->img,round($x),round($y),$this->current_color); + } + + function FillToBorder($x,$y,$aBordColor) { + $bc = $this->rgb->allocate($aBordColor); + if( $bc == -1 ) { + JpGraphError::RaiseL(25106);//('Image::FillToBorder : Can not allocate more colors'); + } + imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color); + } + + function SetExpired($aFlg=true) { + $this->expired = $aFlg; + } + + // Generate image header + function Headers() { + + // In case we are running from the command line with the client version of + // PHP we can't send any headers. + $sapi = php_sapi_name(); + if( $sapi == 'cli' ) + return; + + // These parameters are set by headers_sent() but they might cause + // an undefined variable error unless they are initilized + $file=''; + $lineno=''; + if( headers_sent($file,$lineno) ) { + $file=basename($file); + $t = new ErrMsgText(); + $msg = $t->Get(10,$file,$lineno); + die($msg); + } + + if ($this->expired) { + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + } + header("Content-type: image/$this->img_format"); + } + + // Adjust image quality for formats that allow this + function SetQuality($q) { + $this->quality = $q; + } + + // Stream image to browser or to file + function Stream($aFile="") { + $func="image".$this->img_format; + if( $this->img_format=="jpeg" && $this->quality != null ) { + $res = @$func($this->img,$aFile,$this->quality); + } + else { + if( $aFile != "" ) { + $res = @$func($this->img,$aFile); + if( !$res ) + JpGraphError::RaiseL(25107,$aFile);//("Can't write to file '$aFile'. Check that the process running PHP has enough permission."); + } + else { + $res = @$func($this->img); + if( !$res ) + JpGraphError::RaiseL(25108);//("Can't stream image. This is most likely due to a faulty PHP/GD setup. Try to recompile PHP and use the built-in GD library that comes with PHP."); + + } + } + } + + // Clear resource tide up by image + function Destroy() { + imagedestroy($this->img); + } + + // Specify image format. Note depending on your installation + // of PHP not all formats may be supported. + function SetImgFormat($aFormat,$aQuality=75) { + $this->quality = $aQuality; + $aFormat = strtolower($aFormat); + $tst = true; + $supported = imagetypes(); + if( $aFormat=="auto" ) { + if( $supported & IMG_PNG ) + $this->img_format="png"; + elseif( $supported & IMG_JPG ) + $this->img_format="jpeg"; + elseif( $supported & IMG_GIF ) + $this->img_format="gif"; + elseif( $supported & IMG_WBMP ) + $this->img_format="wbmp"; + elseif( $supported & IMG_XPM ) + $this->img_format="xpm"; + else + JpGraphError::RaiseL(25109);//("Your PHP (and GD-lib) installation does not appear to support any known graphic formats. You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images you must get the JPEG library. Please see the PHP docs for details."); + + return true; + } + else { + if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) { + if( $aFormat=="jpeg" && !($supported & IMG_JPG) ) + $tst=false; + elseif( $aFormat=="png" && !($supported & IMG_PNG) ) + $tst=false; + elseif( $aFormat=="gif" && !($supported & IMG_GIF) ) + $tst=false; + elseif( $aFormat=="wbmp" && !($supported & IMG_WBMP) ) + $tst=false; + elseif( $aFormat=="xpm" && !($supported & IMG_XPM) ) + $tst=false; + else { + $this->img_format=$aFormat; + return true; + } + } + else + $tst=false; + if( !$tst ) + JpGraphError::RaiseL(25110,$aFormat);//(" Your PHP installation does not support the chosen graphic format: $aFormat"); + } + } +} // CLASS + +//=================================================== +// CLASS RotImage +// Description: Exactly as Image but draws the image at +// a specified angle around a specified rotation point. +//=================================================== +class RotImage extends Image { + public $a=0; + public $dx=0,$dy=0,$transx=0,$transy=0; + private $m=array(); + + function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT,$aSetAutoMargin=true) { + $this->Image($aWidth,$aHeight,$aFormat,$aSetAutoMargin); + $this->dx=$this->left_margin+$this->plotwidth/2; + $this->dy=$this->top_margin+$this->plotheight/2; + $this->SetAngle($a); + } + + function SetCenter($dx,$dy) { + $old_dx = $this->dx; + $old_dy = $this->dy; + $this->dx=$dx; + $this->dy=$dy; + $this->SetAngle($this->a); + return array($old_dx,$old_dy); + } + + function SetTranslation($dx,$dy) { + $old = array($this->transx,$this->transy); + $this->transx = $dx; + $this->transy = $dy; + return $old; + } + + function UpdateRotMatrice() { + $a = $this->a; + $a *= M_PI/180; + $sa=sin($a); $ca=cos($a); + // Create the rotation matrix + $this->m[0][0] = $ca; + $this->m[0][1] = -$sa; + $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ; + $this->m[1][0] = $sa; + $this->m[1][1] = $ca; + $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ; + } + + function SetAngle($a) { + $tmp = $this->a; + $this->a = $a; + $this->UpdateRotMatrice(); + return $tmp; + } + + function Circle($xc,$yc,$r) { + list($xc,$yc) = $this->Rotate($xc,$yc); + parent::Circle($xc,$yc,$r); + } + + function FilledCircle($xc,$yc,$r) { + list($xc,$yc) = $this->Rotate($xc,$yc); + parent::FilledCircle($xc,$yc,$r); + } + + + function Arc($xc,$yc,$w,$h,$s,$e) { + list($xc,$yc) = $this->Rotate($xc,$yc); + $s += $this->a; + $e += $this->a; + parent::Arc($xc,$yc,$w,$h,$s,$e); + } + + function FilledArc($xc,$yc,$w,$h,$s,$e,$style='') { + list($xc,$yc) = $this->Rotate($xc,$yc); + $s += $this->a; + $e += $this->a; + parent::FilledArc($xc,$yc,$w,$h,$s,$e); + } + + function SetMargin($lm,$rm,$tm,$bm) { + parent::SetMargin($lm,$rm,$tm,$bm); + $this->dx=$this->left_margin+$this->plotwidth/2; + $this->dy=$this->top_margin+$this->plotheight/2; + $this->UpdateRotMatrice(); + } + + function Rotate($x,$y) { + // Optimization. Ignore rotation if Angle==0 || Angle==360 + if( $this->a == 0 || $this->a == 360 ) { + return array($x + $this->transx, $y + $this->transy ); + } + else { + $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx; + $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy; + return array($x1,$y1); + } + } + + function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) { + list($toX,$toY) = $this->Rotate($toX,$toY); + parent::CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight,$aMix); + + } + + function ArrRotate($pnts) { + $n = count($pnts)-1; + for($i=0; $i < $n; $i+=2) { + list ($x,$y) = $this->Rotate($pnts[$i],$pnts[$i+1]); + $pnts[$i] = $x; $pnts[$i+1] = $y; + } + return $pnts; + } + + function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) { + list($x1,$y1) = $this->Rotate($x1,$y1); + list($x2,$y2) = $this->Rotate($x2,$y2); + parent::DashedLine($x1,$y1,$x2,$y2,$dash_length,$dash_space); + } + + function Line($x1,$y1,$x2,$y2) { + list($x1,$y1) = $this->Rotate($x1,$y1); + list($x2,$y2) = $this->Rotate($x2,$y2); + parent::Line($x1,$y1,$x2,$y2); + } + + function Rectangle($x1,$y1,$x2,$y2) { + // Rectangle uses Line() so it will be rotated through that call + parent::Rectangle($x1,$y1,$x2,$y2); + } + + function FilledRectangle($x1,$y1,$x2,$y2) { + if( $y1==$y2 || $x1==$x2 ) + $this->Line($x1,$y1,$x2,$y2); + else + $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2)); + } + + function Polygon($pnts,$closed=FALSE,$fast=FALSE) { + // Polygon uses Line() so it will be rotated through that call unless + // fast drawing routines are used in which case a rotate is needed + if( $fast ) { + parent::Polygon($this->ArrRotate($pnts)); + } + else + parent::Polygon($pnts,$closed,$fast); + } + + function FilledPolygon($pnts) { + parent::FilledPolygon($this->ArrRotate($pnts)); + } + + function Point($x,$y) { + list($xp,$yp) = $this->Rotate($x,$y); + parent::Point($xp,$yp); + } + + function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) { + list($xp,$yp) = $this->Rotate($x,$y); + return parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug); + } +} + +//=================================================== +// CLASS ImgStreamCache +// Description: Handle caching of graphs to files +//=================================================== +class ImgStreamCache { + private $cache_dir, $img=null, $timeout=0; // Infinite timeout + //--------------- + // CONSTRUCTOR + function ImgStreamCache($aImg, $aCacheDir=CACHE_DIR) { + $this->img = $aImg; + $this->cache_dir = $aCacheDir; + } + +//--------------- +// PUBLIC METHODS + + // Specify a timeout (in minutes) for the file. If the file is older then the + // timeout value it will be overwritten with a newer version. + // If timeout is set to 0 this is the same as infinite large timeout and if + // timeout is set to -1 this is the same as infinite small timeout + function SetTimeout($aTimeout) { + $this->timeout=$aTimeout; + } + + // Output image to browser and also write it to the cache + function PutAndStream($aImage,$aCacheFileName,$aInline,$aStrokeFileName) { + // Some debugging code to brand the image with numbe of colors + // used + GLOBAL $gJpgBrandTiming; + + if( $gJpgBrandTiming ) { + global $tim; + $t=$tim->Pop()/1000.0; + $c=$aImage->SetColor("black"); + $t=sprintf(BRAND_TIME_FORMAT,round($t,3)); + imagestring($this->img->img,2,5,$this->img->height-20,$t,$c); + } + + // Check if we should stroke the image to an arbitrary file + if( _FORCE_IMGTOFILE ) { + $aStrokeFileName = _FORCE_IMGDIR.GenImgName(); + } + + if( $aStrokeFileName!="" ) { + if( $aStrokeFileName == "auto" ) + $aStrokeFileName = GenImgName(); + if( file_exists($aStrokeFileName) ) { + // Delete the old file + if( !@unlink($aStrokeFileName) ) + JpGraphError::RaiseL(25111,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?"); + } + $aImage->Stream($aStrokeFileName); + return; + } + + if( $aCacheFileName != "" && USE_CACHE) { + + $aCacheFileName = $this->cache_dir . $aCacheFileName; + if( file_exists($aCacheFileName) ) { + if( !$aInline ) { + // If we are generating image off-line (just writing to the cache) + // and the file exists and is still valid (no timeout) + // then do nothing, just return. + $diff=time()-filemtime($aCacheFileName); + if( $diff < 0 ) + JpGraphError::RaiseL(25112,$aCacheFileName);//(" Cached imagefile ($aCacheFileName) has file date in the future!!"); + if( $this->timeout>0 && ($diff <= $this->timeout*60) ) + return; + } + if( !@unlink($aCacheFileName) ) + JpGraphError::RaiseL(25113,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?"); + $aImage->Stream($aCacheFileName); + } + else { + $this->MakeDirs(dirname($aCacheFileName)); + if( !is_writeable(dirname($aCacheFileName)) ) { + JpGraphError::RaiseL(25114,$aCacheFileName);//('PHP has not enough permissions to write to the cache file '.$aCacheFileName.'. Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.'); + } + $aImage->Stream($aCacheFileName); + } + + $res=true; + // Set group to specified + if( CACHE_FILE_GROUP != "" ) + $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP); + if( CACHE_FILE_MOD != "" ) + $res = @chmod($aCacheFileName,CACHE_FILE_MOD); + if( !$res ) + JpGraphError::RaiseL(25115,$aStrokeFileName);//(" Can't set permission for cached image $aStrokeFileName. Permission problem?"); + + $aImage->Destroy(); + if( $aInline ) { + if ($fh = @fopen($aCacheFileName, "rb") ) { + $this->img->Headers(); + fpassthru($fh); + return; + } + else + JpGraphError::RaiseL(25116,$aFile);//(" Cant open file from cache [$aFile]"); + } + } + elseif( $aInline ) { + $this->img->Headers(); + $aImage->Stream(); + return; + } + } + + // Check if a given image is in cache and in that case + // pass it directly on to web browser. Return false if the + // image file doesn't exist or exists but is to old + function GetAndStream($aCacheFileName) { + $aCacheFileName = $this->cache_dir.$aCacheFileName; + if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) { + $diff=time()-filemtime($aCacheFileName); + if( $this->timeout>0 && ($diff > $this->timeout*60) ) { + return false; + } + else { + if ($fh = @fopen($aCacheFileName, "rb")) { + $this->img->Headers(); + fpassthru($fh); + return true; + } + else + JpGraphError::RaiseL(25117,$aCacheFileName);//(" Can't open cached image \"$aCacheFileName\" for reading."); + } + } + return false; + } + + //--------------- + // PRIVATE METHODS + // Create all necessary directories in a path + function MakeDirs($aFile) { + $dirs = array(); + while ( !(file_exists($aFile)) ) { + $dirs[] = $aFile; + $aFile = dirname($aFile); + } + for ($i = sizeof($dirs)-1; $i>=0; $i--) { + if(! @mkdir($dirs[$i],0777) ) + JpGraphError::RaiseL(25118,$aFile);//(" Can't create directory $aFile. Make sure PHP has write permission to this directory."); + // We also specify mode here after we have changed group. + // This is necessary if Apache user doesn't belong the + // default group and hence can't specify group permission + // in the previous mkdir() call + if( CACHE_FILE_GROUP != "" ) { + $res=true; + $res =@chgrp($dirs[$i],CACHE_FILE_GROUP); + $res = @chmod($dirs[$i],0777); + if( !$res ) + JpGraphError::RaiseL(25119,$aFile);//(" Can't set permissions for $aFile. Permission problems?"); + } + } + return true; + } +} // CLASS Cache + + +?> diff --git a/src/classes/jpgraph/jpg-config.inc.php b/src/classes/jpgraph/jpg-config.inc.php new file mode 100644 index 0000000..dbf2726 --- /dev/null +++ b/src/classes/jpgraph/jpg-config.inc.php @@ -0,0 +1,227 @@ + diff --git a/src/classes/jpgraph/jpgraph.php b/src/classes/jpgraph/jpgraph.php new file mode 100644 index 0000000..9626fac --- /dev/null +++ b/src/classes/jpgraph/jpgraph.php @@ -0,0 +1,6391 @@ +Get(11,$file,$lineno); + die($msg); + } + else { + DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/'); + } + } else { + DEFINE('CACHE_DIR','/tmp/jpgraph_cache/'); + } + } +} +elseif( !defined('CACHE_DIR') ) { + DEFINE('CACHE_DIR', ''); +} + +if (!defined('TTF_DIR')) { + if (strstr( PHP_OS, 'WIN') ) { + $sroot = getenv('SystemRoot'); + if( empty($sroot) ) { + $t = new ErrMsgText(); + $msg = $t->Get(12,$file,$lineno); + die($msg); + } + else { + DEFINE('TTF_DIR', $sroot.'/fonts/'); + } + } else { + DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/'); + } +} + +if (!defined('MBTTF_DIR')) { + DEFINE('MBTTF_DIR','/usr/share/fonts/ja/TrueType/'); +} + +//------------------------------------------------------------------ +// Constants which are used as parameters for the method calls +//------------------------------------------------------------------ + + +// Tick density +DEFINE("TICKD_DENSE",1); +DEFINE("TICKD_NORMAL",2); +DEFINE("TICKD_SPARSE",3); +DEFINE("TICKD_VERYSPARSE",4); + +// Side for ticks and labels. +DEFINE("SIDE_LEFT",-1); +DEFINE("SIDE_RIGHT",1); +DEFINE("SIDE_DOWN",-1); +DEFINE("SIDE_BOTTOM",-1); +DEFINE("SIDE_UP",1); +DEFINE("SIDE_TOP",1); + +// Legend type stacked vertical or horizontal +DEFINE("LEGEND_VERT",0); +DEFINE("LEGEND_HOR",1); + +// Mark types for plot marks +DEFINE("MARK_SQUARE",1); +DEFINE("MARK_UTRIANGLE",2); +DEFINE("MARK_DTRIANGLE",3); +DEFINE("MARK_DIAMOND",4); +DEFINE("MARK_CIRCLE",5); +DEFINE("MARK_FILLEDCIRCLE",6); +DEFINE("MARK_CROSS",7); +DEFINE("MARK_STAR",8); +DEFINE("MARK_X",9); +DEFINE("MARK_LEFTTRIANGLE",10); +DEFINE("MARK_RIGHTTRIANGLE",11); +DEFINE("MARK_FLASH",12); +DEFINE("MARK_IMG",13); +DEFINE("MARK_FLAG1",14); +DEFINE("MARK_FLAG2",15); +DEFINE("MARK_FLAG3",16); +DEFINE("MARK_FLAG4",17); + +// Builtin images +DEFINE("MARK_IMG_PUSHPIN",50); +DEFINE("MARK_IMG_SPUSHPIN",50); +DEFINE("MARK_IMG_LPUSHPIN",51); +DEFINE("MARK_IMG_DIAMOND",52); +DEFINE("MARK_IMG_SQUARE",53); +DEFINE("MARK_IMG_STAR",54); +DEFINE("MARK_IMG_BALL",55); +DEFINE("MARK_IMG_SBALL",55); +DEFINE("MARK_IMG_MBALL",56); +DEFINE("MARK_IMG_LBALL",57); +DEFINE("MARK_IMG_BEVEL",58); + +// Inline defines +DEFINE("INLINE_YES",1); +DEFINE("INLINE_NO",0); + +// Format for background images +DEFINE("BGIMG_FILLPLOT",1); +DEFINE("BGIMG_FILLFRAME",2); +DEFINE("BGIMG_COPY",3); +DEFINE("BGIMG_CENTER",4); + +// Depth of objects +DEFINE("DEPTH_BACK",0); +DEFINE("DEPTH_FRONT",1); + +// Direction +DEFINE("VERTICAL",1); +DEFINE("HORIZONTAL",0); + + +// Axis styles for scientific style axis +DEFINE('AXSTYLE_SIMPLE',1); +DEFINE('AXSTYLE_BOXIN',2); +DEFINE('AXSTYLE_BOXOUT',3); +DEFINE('AXSTYLE_YBOXIN',4); +DEFINE('AXSTYLE_YBOXOUT',5); + +// Style for title backgrounds +DEFINE('TITLEBKG_STYLE1',1); +DEFINE('TITLEBKG_STYLE2',2); +DEFINE('TITLEBKG_STYLE3',3); +DEFINE('TITLEBKG_FRAME_NONE',0); +DEFINE('TITLEBKG_FRAME_FULL',1); +DEFINE('TITLEBKG_FRAME_BOTTOM',2); +DEFINE('TITLEBKG_FRAME_BEVEL',3); +DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1); +DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2); +DEFINE('TITLEBKG_FILLSTYLE_SOLID',3); + +// Style for background gradient fills +DEFINE('BGRAD_FRAME',1); +DEFINE('BGRAD_MARGIN',2); +DEFINE('BGRAD_PLOT',3); + +// Width of tab titles +DEFINE('TABTITLE_WIDTHFIT',0); +DEFINE('TABTITLE_WIDTHFULL',-1); + +// Defines for 3D skew directions +DEFINE('SKEW3D_UP',0); +DEFINE('SKEW3D_DOWN',1); +DEFINE('SKEW3D_LEFT',2); +DEFINE('SKEW3D_RIGHT',3); + +// Line styles +DEFINE('LINESTYLE_SOLID',1); +DEFINE('LINESTYLE_DOTTED',2); +DEFINE('LINESTYLE_DASHED',3); +DEFINE('LINESTYLE_LONGDASH',4); + +// For internal use only +DEFINE("_JPG_DEBUG",false); +DEFINE("_FORCE_IMGTOFILE",false); +DEFINE("_FORCE_IMGDIR",'/tmp/jpgimg/'); + +require_once('gd_image.inc.php'); + +function CheckPHPVersion($aMinVersion) +{ + list($majorC, $minorC, $editC) = split('[/.-]', PHP_VERSION); + list($majorR, $minorR, $editR) = split('[/.-]', $aMinVersion); + + if ($majorC != $majorR) return false; + if ($majorC < $majorR) return false; + // same major - check ninor + if ($minorC > $minorR) return true; + if ($minorC < $minorR) return false; + // and same minor + if ($editC >= $editR) return true; + return true; +} + +// +// Make sure PHP version is high enough +// +if( !CheckPHPVersion(MIN_PHPVERSION) ) { + JpGraphError::RaiseL(13,PHP_VERSION,MIN_PHPVERSION); + die(); +} + + +// +// Make GD sanity check +// +if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) { + JpGraphError::RaiseL(25001); +//("This PHP installation is not configured with the GD library. Please recompile PHP with GD support to run JpGraph. (Neither function imagetypes() nor imagecreatefromstring() does exist)"); +} + +// +// Setup PHP error handler +// +function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) { + // Respect current error level + if( $errno & error_reporting() ) { + JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg); + } +} + +if( INSTALL_PHP_ERR_HANDLER ) { + set_error_handler("_phpErrorHandler"); +} + +// +//Check if there were any warnings, perhaps some wrong includes by the +//user +// +if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG && + !preg_match('/|Deprecated|/i', $GLOBALS['php_errormsg']) ) { + JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']); +} + + +// Useful mathematical function +function sign($a) {return $a >= 0 ? 1 : -1;} + +// Utility function to generate an image name based on the filename we +// are running from and assuming we use auto detection of graphic format +// (top level), i.e it is safe to call this function +// from a script that uses JpGraph +function GenImgName() { + // Determine what format we should use when we save the images + $supported = imagetypes(); + if( $supported & IMG_PNG ) $img_format="png"; + elseif( $supported & IMG_GIF ) $img_format="gif"; + elseif( $supported & IMG_JPG ) $img_format="jpeg"; + elseif( $supported & IMG_WBMP ) $img_format="wbmp"; + elseif( $supported & IMG_XPM ) $img_format="xpm"; + + + if( !isset($_SERVER['PHP_SELF']) ) + JpGraphError::RaiseL(25005); +//(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line if you want to use the 'auto' naming of cache or image files."); + $fname = basename($_SERVER['PHP_SELF']); + if( !empty($_SERVER['QUERY_STRING']) ) { + $q = @$_SERVER['QUERY_STRING']; + $fname .= '_'.preg_replace("/\W/", "_", $q).'.'.$img_format; + } + else { + $fname = substr($fname,0,strlen($fname)-4).'.'.$img_format; + } + return $fname; +} + + +//=================================================== +// CLASS JpgTimer +// Description: General timing utility class to handle +// time measurement of generating graphs. Multiple +// timers can be started. +//=================================================== +class JpgTimer { + private $start, $idx; +//--------------- +// CONSTRUCTOR + function JpgTimer() { + $this->idx=0; + } + +//--------------- +// PUBLIC METHODS + + // Push a new timer start on stack + function Push() { + list($ms,$s)=explode(" ",microtime()); + $this->start[$this->idx++]=floor($ms*1000) + 1000*$s; + } + + // Pop the latest timer start and return the diff with the + // current time + function Pop() { + assert($this->idx>0); + list($ms,$s)=explode(" ",microtime()); + $etime=floor($ms*1000) + (1000*$s); + $this->idx--; + return $etime-$this->start[$this->idx]; + } +} // Class + +$gJpgBrandTiming = BRAND_TIMING; +//=================================================== +// CLASS DateLocale +// Description: Hold localized text used in dates +//=================================================== +class DateLocale { + + public $iLocale = 'C'; // environmental locale be used by default + private $iDayAbb = null, $iShortDay = null, $iShortMonth = null, $iMonthName = null; + +//--------------- +// CONSTRUCTOR + function DateLocale() { + settype($this->iDayAbb, 'array'); + settype($this->iShortDay, 'array'); + settype($this->iShortMonth, 'array'); + settype($this->iMonthName, 'array'); + + + $this->Set('C'); + } + +//--------------- +// PUBLIC METHODS + function Set($aLocale) { + if ( in_array($aLocale, array_keys($this->iDayAbb)) ){ + $this->iLocale = $aLocale; + return TRUE; // already cached nothing else to do! + } + + $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME + + if (is_array($aLocale)) { + foreach ($aLocale as $loc) { + $res = @setlocale(LC_TIME, $loc); + if ( $res ) { + $aLocale = $loc; + break; + } + } + } + else { + $res = @setlocale(LC_TIME, $aLocale); + } + + if ( ! $res ){ + JpGraphError::RaiseL(25007,$aLocale); +//("You are trying to use the locale ($aLocale) which your PHP installation does not support. Hint: Use '' to indicate the default locale for this geographic region."); + return FALSE; + } + + $this->iLocale = $aLocale; + for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){ + $day = strftime('%a', strtotime("$ofs day")); + $day[0] = strtoupper($day[0]); + $this->iDayAbb[$aLocale][]= $day[0]; + $this->iShortDay[$aLocale][]= $day; + } + + for($i=1; $i<=12; ++$i) { + list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01"))); + $this->iShortMonth[$aLocale][] = ucfirst($short); + $this->iMonthName [$aLocale][] = ucfirst($full); + } + + setlocale(LC_TIME, $pLocale); + + return TRUE; + } + + + function GetDayAbb() { + return $this->iDayAbb[$this->iLocale]; + } + + function GetShortDay() { + return $this->iShortDay[$this->iLocale]; + } + + function GetShortMonth() { + return $this->iShortMonth[$this->iLocale]; + } + + function GetShortMonthName($aNbr) { + return $this->iShortMonth[$this->iLocale][$aNbr]; + } + + function GetLongMonthName($aNbr) { + return $this->iMonthName[$this->iLocale][$aNbr]; + } + + function GetMonth() { + return $this->iMonthName[$this->iLocale]; + } +} + +$gDateLocale = new DateLocale(); +$gJpgDateLocale = new DateLocale(); + +//======================================================= +// CLASS Footer +// Description: Encapsulates the footer line in the Graph +//======================================================= +class Footer { + public $iLeftMargin = 3, $iRightMargin = 3, $iBottomMargin = 3 ; + public $left,$center,$right; + + function Footer() { + $this->left = new Text(); + $this->left->ParagraphAlign('left'); + $this->center = new Text(); + $this->center->ParagraphAlign('center'); + $this->right = new Text(); + $this->right->ParagraphAlign('right'); + } + + function SetMargin($aLeft=3,$aRight=3,$aBottom=3) { + $this->iLeftMargin = $aLeft; + $this->iRightMargin = $aRight; + $this->iBottomMargin = $aBottom; + } + + function Stroke($aImg) { + $y = $aImg->height - $this->iBottomMargin; + $x = $this->iLeftMargin; + $this->left->Align('left','bottom'); + $this->left->Stroke($aImg,$x,$y); + + $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2; + $this->center->Align('center','bottom'); + $this->center->Stroke($aImg,$x,$y); + + $x = $aImg->width - $this->iRightMargin; + $this->right->Align('right','bottom'); + $this->right->Stroke($aImg,$x,$y); + } +} + + +//=================================================== +// CLASS Graph +// Description: Main class to handle graphs +//=================================================== +class Graph { + public $cache=null; // Cache object (singleton) + public $img=null; // Img object (singleton) + public $plots=array(); // Array of all plot object in the graph (for Y 1 axis) + public $y2plots=array();// Array of all plot object in the graph (for Y 2 axis) + public $ynplots=array(); + public $xscale=null; // X Scale object (could be instance of LinearScale or LogScale + public $yscale=null,$y2scale=null, $ynscale=array(); + public $iIcons = array(); // Array of Icons to add to + public $cache_name; // File name to be used for the current graph in the cache directory + public $xgrid=null; // X Grid object (linear or logarithmic) + public $ygrid=null,$y2grid=null; //dito for Y + public $doframe=true,$frame_color=array(0,0,0), $frame_weight=1; // Frame around graph + public $boxed=false, $box_color=array(0,0,0), $box_weight=1; // Box around plot area + public $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102); // Shadow for graph + public $xaxis=null; // X-axis (instane of Axis class) + public $yaxis=null, $y2axis=null, $ynaxis=array(); // Y axis (instance of Axis class) + public $margin_color=array(200,200,200); // Margin color of graph + public $plotarea_color=array(255,255,255); // Plot area color + public $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object + public $axtype="linlin"; // Type of axis + public $xtick_factor,$ytick_factor; // Factor to determine the maximum number of ticks depending on the plot width + public $texts=null, $y2texts=null; // Text object to ge shown in the graph + public $lines=null, $y2lines=null; + public $bands=null, $y2bands=null; + public $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale in fractions and for centering bars + public $background_image="",$background_image_type=-1,$background_image_format="png"; + public $background_image_bright=0,$background_image_contr=0,$background_image_sat=0; + public $image_bright=0, $image_contr=0, $image_sat=0; + public $inline; + public $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0 + public $grid_depth=DEPTH_BACK; // Draw grid under all plots as default + public $iAxisStyle = AXSTYLE_SIMPLE; + public $iCSIMdisplay=false,$iHasStroked = false; + public $footer; + public $csimcachename = '', $csimcachetimeout = 0, $iCSIMImgAlt=''; + public $iDoClipping = false; + public $y2orderback=true; + public $tabtitle; + public $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN; + public $bkg_gradfrom='navy', $bkg_gradto='silver'; + public $titlebackground = false; + public $titlebackground_color = 'lightblue', + $titlebackground_style = 1, + $titlebackground_framecolor = 'blue', + $titlebackground_framestyle = 2, + $titlebackground_frameweight = 1, + $titlebackground_bevelheight = 3 ; + public $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID; + public $titlebkg_scolor1='black',$titlebkg_scolor2='white'; + public $framebevel = false, $framebeveldepth = 2 ; + public $framebevelborder = false, $framebevelbordercolor='black'; + public $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4'; + public $background_image_mix=100; + public $background_cflag = ''; + public $background_cflag_type = BGIMG_FILLPLOT; + public $background_cflag_mix = 100; + public $iImgTrans=false, + $iImgTransHorizon = 100,$iImgTransSkewDist=150, + $iImgTransDirection = 1, $iImgTransMinSize = true, + $iImgTransFillColor='white',$iImgTransHighQ=false, + $iImgTransBorder=false,$iImgTransHorizonPos=0.5; + public $legend; + protected $iYAxisDeltaPos=50; + protected $iIconDepth=DEPTH_BACK; + protected $iAxisLblBgType = 0, + $iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black', + $iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black'; + protected $iTables=NULL; + +//--------------- +// CONSTRUCTOR + + // aWIdth Width in pixels of image + // aHeight Height in pixels of image + // aCachedName Name for image file in cache directory + // aTimeOut Timeout in minutes for image in cache + // aInline If true the image is streamed back in the call to Stroke() + // If false the image is just created in the cache + function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) { + GLOBAL $gJpgBrandTiming; + // If timing is used create a new timing object + if( $gJpgBrandTiming ) { + global $tim; + $tim = new JpgTimer(); + $tim->Push(); + } + + if( !is_numeric($aWidth) || !is_numeric($aHeight) ) { + JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric'); + } + + // Automatically generate the image file name based on the name of the script that + // generates the graph + if( $aCachedName=="auto" ) + $aCachedName=GenImgName(); + + // Should the image be streamed back to the browser or only to the cache? + $this->inline=$aInline; + + $this->img = new RotImage($aWidth,$aHeight); + + $this->cache = new ImgStreamCache($this->img); + $this->cache->SetTimeOut($aTimeOut); + + $this->title = new Text(); + $this->title->ParagraphAlign('center'); + $this->title->SetFont(FF_FONT2,FS_BOLD); + $this->title->SetMargin(3); + $this->title->SetAlign('center'); + + $this->subtitle = new Text(); + $this->subtitle->ParagraphAlign('center'); + $this->subtitle->SetMargin(2); + $this->subtitle->SetAlign('center'); + + $this->subsubtitle = new Text(); + $this->subsubtitle->ParagraphAlign('center'); + $this->subsubtitle->SetMargin(2); + $this->subsubtitle->SetAlign('center'); + + $this->legend = new Legend(); + $this->footer = new Footer(); + + // Window doesn't like '?' in the file name so replace it with an '_' + $aCachedName = str_replace("?","_",$aCachedName); + + // If the cached version exist just read it directly from the + // cache, stream it back to browser and exit + if( $aCachedName!="" && READ_CACHE && $aInline ) + if( $this->cache->GetAndStream($aCachedName) ) { + exit(); + } + + $this->cache_name = $aCachedName; + $this->SetTickDensity(); // Normal density + + $this->tabtitle = new GraphTabTitle(); + } +//--------------- +// PUBLIC METHODS + + // Enable final image perspective transformation + function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) { + $this->iImgTrans = true; + $this->iImgTransHorizon = $aHorizon; + $this->iImgTransSkewDist= $aSkewDist; + $this->iImgTransDirection = $aDir; + $this->iImgTransMinSize = $aMinSize; + $this->iImgTransFillColor=$aFillColor; + $this->iImgTransHighQ=$aQuality; + $this->iImgTransBorder=$aBorder; + $this->iImgTransHorizonPos=$aHorizonPos; + } + + // Set Image format and optional quality + function SetImgFormat($aFormat,$aQuality=75) { + $this->img->SetImgFormat($aFormat,$aQuality); + } + + // Should the grid be in front or back of the plot? + function SetGridDepth($aDepth) { + $this->grid_depth=$aDepth; + } + + function SetIconDepth($aDepth) { + $this->iIconDepth=$aDepth; + } + + // Specify graph angle 0-360 degrees. + function SetAngle($aAngle) { + $this->img->SetAngle($aAngle); + } + + function SetAlphaBlending($aFlg=true) { + $this->img->SetAlphaBlending($aFlg); + } + + // Shortcut to image margin + function SetMargin($lm,$rm,$tm,$bm) { + $this->img->SetMargin($lm,$rm,$tm,$bm); + } + + function SetY2OrderBack($aBack=true) { + $this->y2orderback = $aBack; + } + + // Rotate the graph 90 degrees and set the margin + // when we have done a 90 degree rotation + function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) { + $lm = $lm ==0 ? floor(0.2 * $this->img->width) : $lm ; + $rm = $rm ==0 ? floor(0.1 * $this->img->width) : $rm ; + $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ; + $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ; + + $adj = ($this->img->height - $this->img->width)/2; + $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj); + $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2)); + $this->SetAngle(90); + if( empty($this->yaxis) || empty($this->xaxis) ) { + JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()'); + } + $this->xaxis->SetLabelAlign('right','center'); + $this->yaxis->SetLabelAlign('center','bottom'); + } + + function SetClipping($aFlg=true) { + $this->iDoClipping = $aFlg ; + } + + // Add a plot object to the graph + function Add($aPlot) { + if( $aPlot == null ) + JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph."); + if( is_array($aPlot) && count($aPlot) > 0 ) + $cl = $aPlot[0]; + else + $cl = $aPlot; + + if( $cl instanceof Text ) + $this->AddText($aPlot); + elseif( $cl instanceof PlotLine ) + $this->AddLine($aPlot); + elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) + $this->AddBand($aPlot); + elseif( class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) + $this->AddIcon($aPlot); + elseif( class_exists('GTextTable',false) && ($cl instanceof GTextTable) ) + $this->AddTable($aPlot); + else + $this->plots[] = $aPlot; + } + + function AddTable($aTable) { + if( is_array($aTable) ) { + for($i=0; $i < count($aTable); ++$i ) + $this->iTables[]=$aTable[$i]; + } + else { + $this->iTables[] = $aTable ; + } + } + + function AddIcon($aIcon) { + if( is_array($aIcon) ) { + for($i=0; $i < count($aIcon); ++$i ) + $this->iIcons[]=$aIcon[$i]; + } + else { + $this->iIcons[] = $aIcon ; + } + } + + // Add plot to second Y-scale + function AddY2($aPlot) { + if( $aPlot == null ) + JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph."); + + if( is_array($aPlot) && count($aPlot) > 0 ) + $cl = $aPlot[0]; + else + $cl = $aPlot; + + if( $cl instanceof Text ) + $this->AddText($aPlot,true); + elseif( $cl instanceof PlotLine ) + $this->AddLine($aPlot,true); + elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) + $this->AddBand($aPlot,true); + else + $this->y2plots[] = $aPlot; + } + + // Add plot to the extra Y-axises + function AddY($aN,$aPlot) { + + if( $aPlot == null ) + JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph."); + + if( is_array($aPlot) && count($aPlot) > 0 ) + $cl = $aPlot[0]; + else + $cl = $aPlot; + + if( ($cl instanceof Text) || ($cl instanceof PlotLine) || + (class_exists('PlotBand',false) && ($cl instanceof PlotBand)) ) + JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis'); + else + $this->ynplots[$aN][] = $aPlot; + } + + // Add text object to the graph + function AddText($aTxt,$aToY2=false) { + if( $aTxt == null ) + JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph."); + if( $aToY2 ) { + if( is_array($aTxt) ) { + for($i=0; $i < count($aTxt); ++$i ) + $this->y2texts[]=$aTxt[$i]; + } + else + $this->y2texts[] = $aTxt; + } + else { + if( is_array($aTxt) ) { + for($i=0; $i < count($aTxt); ++$i ) + $this->texts[]=$aTxt[$i]; + } + else + $this->texts[] = $aTxt; + } + } + + // Add a line object (class PlotLine) to the graph + function AddLine($aLine,$aToY2=false) { + if( $aLine == null ) + JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph."); + + if( $aToY2 ) { + if( is_array($aLine) ) { + for($i=0; $i < count($aLine); ++$i ) + $this->y2lines[]=$aLine[$i]; + } + else + $this->y2lines[] = $aLine; + } + else { + if( is_array($aLine) ) { + for($i=0; $ilines[]=$aLine[$i]; + } + else + $this->lines[] = $aLine; + } + } + + // Add vertical or horizontal band + function AddBand($aBand,$aToY2=false) { + if( $aBand == null ) + JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph."); + + if( $aToY2 ) { + if( is_array($aBand) ) { + for($i=0; $i < count($aBand); ++$i ) + $this->y2bands[] = $aBand[$i]; + } + else + $this->y2bands[] = $aBand; + } + else { + if( is_array($aBand) ) { + for($i=0; $i < count($aBand); ++$i ) + $this->bands[] = $aBand[$i]; + } + else + $this->bands[] = $aBand; + } + } + + function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) { + $this->bkg_gradtype=$aGradType; + $this->bkg_gradstyle=$aStyle; + $this->bkg_gradfrom = $aFrom; + $this->bkg_gradto = $aTo; + } + + // Set a country flag in the background + function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) { + $this->background_cflag = $aName; + $this->background_cflag_type = $aBgType; + $this->background_cflag_mix = $aMix; + } + + // Alias for the above method + function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) { + $this->background_cflag = $aName; + $this->background_cflag_type = $aBgType; + $this->background_cflag_mix = $aMix; + } + + + // Specify a background image + function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") { + + if( !USE_TRUECOLOR ) { + JpGraphError::RaiseL(25017);//("You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x you must enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts."); + } + + // Get extension to determine image type + if( $aImgFormat == "auto" ) { + $e = explode('.',$aFileName); + if( !$e ) { + JpGraphError::RaiseL(25018,$aFileName);//('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type'); + } + + $valid_formats = array('png', 'jpg', 'gif'); + $aImgFormat = strtolower($e[count($e)-1]); + if ($aImgFormat == 'jpeg') { + $aImgFormat = 'jpg'; + } + elseif (!in_array($aImgFormat, $valid_formats) ) { + JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName); + } + } + + $this->background_image = $aFileName; + $this->background_image_type=$aBgType; + $this->background_image_format=$aImgFormat; + } + + function SetBackgroundImageMix($aMix) { + $this->background_image_mix = $aMix ; + } + + // Specify axis style (boxed or single) + function SetAxisStyle($aStyle) { + $this->iAxisStyle = $aStyle ; + } + + // Set a frame around the plot area + function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) { + $this->boxed = $aDrawPlotFrame; + $this->box_weight = $aPlotFrameWeight; + $this->box_color = $aPlotFrameColor; + } + + // Specify color for the plotarea (not the margins) + function SetColor($aColor) { + $this->plotarea_color=$aColor; + } + + // Specify color for the margins (all areas outside the plotarea) + function SetMarginColor($aColor) { + $this->margin_color=$aColor; + } + + // Set a frame around the entire image + function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) { + $this->doframe = $aDrawImgFrame; + $this->frame_color = $aImgFrameColor; + $this->frame_weight = $aImgFrameWeight; + } + + function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) { + $this->framebevel = $aFlg ; + $this->framebeveldepth = $aDepth ; + $this->framebevelborder = $aBorder ; + $this->framebevelbordercolor = $aBorderColor ; + $this->framebevelcolor1 = $aColor1 ; + $this->framebevelcolor2 = $aColor2 ; + + $this->doshadow = false ; + } + + // Set the shadow around the whole image + function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) { + $this->doshadow = $aShowShadow; + $this->shadow_color = $aShadowColor; + $this->shadow_width = $aShadowWidth; + $this->footer->iBottomMargin += $aShadowWidth; + $this->footer->iRightMargin += $aShadowWidth; + } + + // Specify x,y scale. Note that if you manually specify the scale + // you must also specify the tick distance with a call to Ticks::Set() + function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) { + $this->axtype = $aAxisType; + + if( $aYMax < $aYMin || $aXMax < $aXMin ) + JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.'); + + $yt=substr($aAxisType,-3,3); + if( $yt=="lin" ) + $this->yscale = new LinearScale($aYMin,$aYMax); + elseif( $yt == "int" ) { + $this->yscale = new LinearScale($aYMin,$aYMax); + $this->yscale->SetIntScale(); + } + elseif( $yt=="log" ) + $this->yscale = new LogScale($aYMin,$aYMax); + else + JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)"); + + $xt=substr($aAxisType,0,3); + if( $xt == "lin" || $xt == "tex" ) { + $this->xscale = new LinearScale($aXMin,$aXMax,"x"); + $this->xscale->textscale = ($xt == "tex"); + } + elseif( $xt == "int" ) { + $this->xscale = new LinearScale($aXMin,$aXMax,"x"); + $this->xscale->SetIntScale(); + } + elseif( $xt == "dat" ) { + $this->xscale = new DateScale($aXMin,$aXMax,"x"); + } + elseif( $xt == "log" ) + $this->xscale = new LogScale($aXMin,$aXMax,"x"); + else + JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)"); + + $this->xaxis = new Axis($this->img,$this->xscale); + $this->yaxis = new Axis($this->img,$this->yscale); + $this->xgrid = new Grid($this->xaxis); + $this->ygrid = new Grid($this->yaxis); + $this->ygrid->Show(); + } + + // Specify secondary Y scale + function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) { + if( $aAxisType=="lin" ) + $this->y2scale = new LinearScale($aY2Min,$aY2Max); + elseif( $aAxisType == "int" ) { + $this->y2scale = new LinearScale($aY2Min,$aY2Max); + $this->y2scale->SetIntScale(); + } + elseif( $aAxisType=="log" ) { + $this->y2scale = new LogScale($aY2Min,$aY2Max); + } + else JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)"); + + $this->y2axis = new Axis($this->img,$this->y2scale); + $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT); + $this->y2axis->SetLabelSide(SIDE_RIGHT); + $this->y2axis->SetPos('max'); + $this->y2axis->SetTitleSide(SIDE_RIGHT); + + // Deafult position is the max x-value + $this->y2grid = new Grid($this->y2axis); + } + + // Set the delta position (in pixels) between the multiple Y-axis + function SetYDeltaDist($aDist) { + $this->iYAxisDeltaPos = $aDist; + } + + // Specify secondary Y scale + function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) { + + if( $aAxisType=="lin" ) + $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax); + elseif( $aAxisType == "int" ) { + $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax); + $this->ynscale[$aN]->SetIntScale(); + } + elseif( $aAxisType=="log" ) { + $this->ynscale[$aN] = new LogScale($aYMin,$aYMax); + } + else JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)"); + + $this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]); + $this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT); + $this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT); + } + + // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse' + // The dividing factor have been determined heuristically according to my aesthetic + // sense (or lack off) y.m.m.v ! + function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) { + $this->xtick_factor=30; + $this->ytick_factor=25; + switch( $aYDensity ) { + case TICKD_DENSE: + $this->ytick_factor=12; + break; + case TICKD_NORMAL: + $this->ytick_factor=25; + break; + case TICKD_SPARSE: + $this->ytick_factor=40; + break; + case TICKD_VERYSPARSE: + $this->ytick_factor=100; + break; + default: + JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy"); + } + switch( $aXDensity ) { + case TICKD_DENSE: + $this->xtick_factor=15; + break; + case TICKD_NORMAL: + $this->xtick_factor=30; + break; + case TICKD_SPARSE: + $this->xtick_factor=45; + break; + case TICKD_VERYSPARSE: + $this->xtick_factor=60; + break; + default: + JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx"); + } + } + + + // Get a string of all image map areas + function GetCSIMareas() { + if( !$this->iHasStroked ) + $this->Stroke(_CSIM_SPECIALFILE); + + $csim = $this->title->GetCSIMAreas(); + $csim .= $this->subtitle->GetCSIMAreas(); + $csim .= $this->subsubtitle->GetCSIMAreas(); + $csim .= $this->legend->GetCSIMAreas(); + + if( $this->y2axis != NULL ) { + $csim .= $this->y2axis->title->GetCSIMAreas(); + } + + if( $this->texts != null ) { + $n = count($this->texts); + for($i=0; $i < $n; ++$i ) { + $csim .= $this->texts[$i]->GetCSIMAreas(); + } + } + + if( $this->y2texts != null && $this->y2scale != null ) { + $n = count($this->y2texts); + for($i=0; $i < $n; ++$i ) { + $csim .= $this->y2texts[$i]->GetCSIMAreas(); + } + } + + if( $this->yaxis != null && $this->xaxis != null ) { + $csim .= $this->yaxis->title->GetCSIMAreas(); + $csim .= $this->xaxis->title->GetCSIMAreas(); + } + + $n = count($this->plots); + for( $i=0; $i < $n; ++$i ) + $csim .= $this->plots[$i]->GetCSIMareas(); + + $n = count($this->y2plots); + for( $i=0; $i < $n; ++$i ) + $csim .= $this->y2plots[$i]->GetCSIMareas(); + + $n = count($this->ynaxis); + for( $i=0; $i < $n; ++$i ) { + $m = count($this->ynplots[$i]); + for($j=0; $j < $m; ++$j ) { + $csim .= $this->ynplots[$i][$j]->GetCSIMareas(); + } + } + + $n = count($this->iTables); + for( $i=0; $i < $n; ++$i ) { + $csim .= $this->iTables[$i]->GetCSIMareas(); + } + + return $csim; + } + + // Get a complete .. tag for the final image map + function GetHTMLImageMap($aMapName) { + $im = "\n"; + $im .= $this->GetCSIMareas(); + $im .= ""; + return $im; + } + + function CheckCSIMCache($aCacheName,$aTimeOut=60) { + global $_SERVER; + + if( $aCacheName=='auto' ) + $aCacheName=basename($_SERVER['PHP_SELF']); + + $urlarg = $this->GetURLArguments(); + $this->csimcachename = CSIMCACHE_DIR.$aCacheName.$urlarg; + $this->csimcachetimeout = $aTimeOut; + + // First determine if we need to check for a cached version + // This differs from the standard cache in the sense that the + // image and CSIM map HTML file is written relative to the directory + // the script executes in and not the specified cache directory. + // The reason for this is that the cache directory is not necessarily + // accessible from the HTTP server. + if( $this->csimcachename != '' ) { + $dir = dirname($this->csimcachename); + $base = basename($this->csimcachename); + $base = strtok($base,'.'); + $suffix = strtok('.'); + $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html'; + $baseimg = $dir.'/'.$base.'?'.$urlarg.'.'.$this->img->img_format; + + $timedout=false; + // Does it exist at all ? + + if( file_exists($basecsim) && file_exists($baseimg) ) { + // Check that it hasn't timed out + $diff=time()-filemtime($basecsim); + if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) { + $timedout=true; + @unlink($basecsim); + @unlink($baseimg); + } + else { + if ($fh = @fopen($basecsim, "r")) { + fpassthru($fh); + return true; + } + else + JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading."); + } + } + } + return false; + } + + // Build the argument string to be used with the csim images + function GetURLArguments() { + + // This is a JPGRAPH internal defined that prevents + // us from recursively coming here again + $urlarg = _CSIM_DISPLAY.'=1'; + + // Now reconstruct any user URL argument + reset($_GET); + while( list($key,$value) = each($_GET) ) { + if( is_array($value) ) { + foreach ( $value as $k => $v ) { + $urlarg .= '&'.$key.'%5B'.$k.'%5D='.urlencode($v); + } + } + else { + $urlarg .= '&'.$key.'='.urlencode($value); + } + } + + // It's not ideal to convert POST argument to GET arguments + // but there is little else we can do. One idea for the + // future might be recreate the POST header in case. + reset($_POST); + while( list($key,$value) = each($_POST) ) { + if( is_array($value) ) { + foreach ( $value as $k => $v ) { + $urlarg .= '&'.$key.'%5B'.$k.'%5D='.urlencode($v); + } + } + else { + $urlarg .= '&'.$key.'='.urlencode($value); + } + } + + return $urlarg; + } + + function SetCSIMImgAlt($aAlt) { + $this->iCSIMImgAlt = $aAlt; + } + + function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) { + if( $aCSIMName=='' ) { + // create a random map name + srand ((double) microtime() * 1000000); + $r = rand(0,100000); + $aCSIMName='__mapname'.$r.'__'; + } + + if( $aScriptName=='auto' ) + $aScriptName=basename($_SERVER['PHP_SELF']); + + $urlarg = $this->GetURLArguments(); + + if( empty($_GET[_CSIM_DISPLAY]) ) { + // First determine if we need to check for a cached version + // This differs from the standard cache in the sense that the + // image and CSIM map HTML file is written relative to the directory + // the script executes in and not the specified cache directory. + // The reason for this is that the cache directory is not necessarily + // accessible from the HTTP server. + if( $this->csimcachename != '' ) { + $dir = dirname($this->csimcachename); + $base = basename($this->csimcachename); + $base = strtok($base,'.'); + $suffix = strtok('.'); + $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html'; + $baseimg = $base.'?'.$urlarg.'.'.$this->img->img_format; + + // Check that apache can write to directory specified + + if( file_exists($dir) && !is_writeable($dir) ) { + JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.'); + } + + // Make sure directory exists + $this->cache->MakeDirs($dir); + + // Write the image file + $this->Stroke(CSIMCACHE_DIR.$baseimg); + + // Construct wrapper HTML and write to file and send it back to browser + + // In the src URL we must replace the '?' with its encoding to prevent the arguments + // to be converted to real arguments. + $tmp = str_replace('?','%3f',$baseimg); + $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n". + '\"".$this-iCSIMImgAlt."\" />\n"; + + if($fh = @fopen($basecsim,'w') ) { + fwrite($fh,$htmlwrap); + fclose($fh); + echo $htmlwrap; + } + else + JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions."); + } + else { + + if( $aScriptName=='' ) { + JpGraphError::RaiseL(25030);//('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().'); + } + echo $this->GetHTMLImageMap($aCSIMName); + echo "\"".$this-iCSIMImgAlt."\" />\n"; + } + } + else { + $this->Stroke(); + } + } + + function GetTextsYMinMax($aY2=false) { + if( $aY2 ) + $txts = $this->y2texts; + else + $txts = $this->texts; + $n = count($txts); + $min=null; + $max=null; + for( $i=0; $i < $n; ++$i ) { + if( $txts[$i]->iScalePosY !== null && + $txts[$i]->iScalePosX !== null ) { + if( $min === null ) { + $min = $max = $txts[$i]->iScalePosY ; + } + else { + $min = min($min,$txts[$i]->iScalePosY); + $max = max($max,$txts[$i]->iScalePosY); + } + } + } + if( $min !== null ) { + return array($min,$max); + } + else + return null; + } + + function GetTextsXMinMax($aY2=false) { + if( $aY2 ) + $txts = $this->y2texts; + else + $txts = $this->texts; + $n = count($txts); + $min=null; + $max=null; + for( $i=0; $i < $n; ++$i ) { + if( $txts[$i]->iScalePosY !== null && + $txts[$i]->iScalePosX !== null ) { + if( $min === null ) { + $min = $max = $txts[$i]->iScalePosX ; + } + else { + $min = min($min,$txts[$i]->iScalePosX); + $max = max($max,$txts[$i]->iScalePosX); + } + } + } + if( $min !== null ) { + return array($min,$max); + } + else + return null; + } + + function GetXMinMax() { + list($min,$ymin) = $this->plots[0]->Min(); + list($max,$ymax) = $this->plots[0]->Max(); + foreach( $this->plots as $p ) { + list($xmin,$ymin) = $p->Min(); + list($xmax,$ymax) = $p->Max(); + $min = Min($xmin,$min); + $max = Max($xmax,$max); + } + + if( $this->y2axis != null ) { + foreach( $this->y2plots as $p ) { + list($xmin,$ymin) = $p->Min(); + list($xmax,$ymax) = $p->Max(); + $min = Min($xmin,$min); + $max = Max($xmax,$max); + } + } + + $n = count($this->ynaxis); + for( $i=0; $i < $n; ++$i ) { + if( $this->ynaxis[$i] != null) { + foreach( $this->ynplots[$i] as $p ) { + list($xmin,$ymin) = $p->Min(); + list($xmax,$ymax) = $p->Max(); + $min = Min($xmin,$min); + $max = Max($xmax,$max); + } + } + } + return array($min,$max); + } + + function AdjustMarginsForTitles() { + $totrequired = + ($this->title->t != '' ? + $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) + + ($this->subtitle->t != '' ? + $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) + + ($this->subsubtitle->t != '' ? + $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ; + + + $btotrequired = 0; + if($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels ) { + // Minimum bottom margin + if( $this->xaxis->title->t != '' ) { + if( $this->img->a == 90 ) + $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ; + else + $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ; + } + else + $btotrequired = 0; + + if( $this->img->a == 90 ) { + $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style, + $this->yaxis->font_size); + $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle); + } + else { + $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style, + $this->xaxis->font_size); + $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle); + } + + $btotrequired += $lh + 5; + } + + if( $this->img->a == 90 ) { + // DO Nothing. It gets too messy to do this properly for 90 deg... + } + else{ + if( $this->img->top_margin < $totrequired ) { + $this->SetMargin($this->img->left_margin,$this->img->right_margin, + $totrequired,$this->img->bottom_margin); + } + if( $this->img->bottom_margin < $btotrequired ) { + $this->SetMargin($this->img->left_margin,$this->img->right_margin, + $this->img->top_margin,$btotrequired); + } + } + } + + // Stroke the graph + // $aStrokeFileName If != "" the image will be written to this file and NOT + // streamed back to the browser + function Stroke($aStrokeFileName="") { + + // Fist make a sanity check that user has specified a scale + if( empty($this->yscale) ) { + JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().'); + } + + // Start by adjusting the margin so that potential titles will fit. + $this->AdjustMarginsForTitles(); + + // Setup scale constants + if( $this->yscale ) $this->yscale->InitConstants($this->img); + if( $this->xscale ) $this->xscale->InitConstants($this->img); + if( $this->y2scale ) $this->y2scale->InitConstants($this->img); + + $n=count($this->ynscale); + for($i=0; $i < $n; ++$i) { + if( $this->ynscale[$i] ) $this->ynscale[$i]->InitConstants($this->img); + } + + // If the filename is the predefined value = '_csim_special_' + // we assume that the call to stroke only needs to do enough + // to correctly generate the CSIM maps. + // We use this variable to skip things we don't strictly need + // to do to generate the image map to improve performance + // a best we can. Therefor you will see a lot of tests !$_csim in the + // code below. + $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE); + + // We need to know if we have stroked the plot in the + // GetCSIMareas. Otherwise the CSIM hasn't been generated + // and in the case of GetCSIM called before stroke to generate + // CSIM without storing an image to disk GetCSIM must call Stroke. + $this->iHasStroked = true; + + // Do any pre-stroke adjustment that is needed by the different plot types + // (i.e bar plots want's to add an offset to the x-labels etc) + for($i=0; $i < count($this->plots) ; ++$i ) { + $this->plots[$i]->PreStrokeAdjust($this); + $this->plots[$i]->DoLegend($this); + } + + // Any plots on the second Y scale? + if( $this->y2scale != null ) { + for($i=0; $iy2plots) ; ++$i ) { + $this->y2plots[$i]->PreStrokeAdjust($this); + $this->y2plots[$i]->DoLegend($this); + } + } + + // Any plots on the extra Y axises? + $n = count($this->ynaxis); + for($i=0; $i<$n ; ++$i ) { + if( $this->ynplots == null || $this->ynplots[$i] == null) { + JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i"); + } + $m = count($this->ynplots[$i]); + for($j=0; $j < $m; ++$j ) { + $this->ynplots[$i][$j]->PreStrokeAdjust($this); + $this->ynplots[$i][$j]->DoLegend($this); + } + } + + // Bail out if any of the Y-axis not been specified and + // has no plots. (This means it is impossible to do autoscaling and + // no other scale was given so we can't possible draw anything). If you use manual + // scaling you also have to supply the tick steps as well. + if( (!$this->yscale->IsSpecified() && count($this->plots)==0) || + ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) { + //$e = "n=".count($this->y2plots)."\n"; + // $e = "Can't draw unspecified Y-scale.
    \nYou have either:
    \n"; + // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots
    \n"; + // $e .= "2. Specified a scale manually but have forgot to specify the tick steps"; + JpGraphError::RaiseL(25026); + } + + // Bail out if no plots and no specified X-scale + if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) ) + JpGraphError::RaiseL(25034);//("JpGraph: Can't draw unspecified X-scale.
    No plots.
    "); + + //Check if we should autoscale y-axis + if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) { + list($min,$max) = $this->GetPlotsYMinMax($this->plots); + $lres = $this->GetLinesYMinMax($this->lines); + if( is_array($lres) ) { + list($linmin,$linmax) = $lres ; + $min = min($min,$linmin); + $max = max($max,$linmax); + } + $tres = $this->GetTextsYMinMax(); + if( is_array($tres) ) { + list($tmin,$tmax) = $tres ; + $min = min($min,$tmin); + $max = max($max,$tmax); + } + $this->yscale->AutoScale($this->img,$min,$max, + $this->img->plotheight/$this->ytick_factor); + } + elseif( $this->yscale->IsSpecified() && + ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) { + // The tick calculation will use the user suplied min/max values to determine + // the ticks. If auto_ticks is false the exact user specifed min and max + // values will be used for the scale. + // If auto_ticks is true then the scale might be slightly adjusted + // so that the min and max values falls on an even major step. + $min = $this->yscale->scale[0]; + $max = $this->yscale->scale[1]; + $this->yscale->AutoScale($this->img,$min,$max, + $this->img->plotheight/$this->ytick_factor, + $this->yscale->auto_ticks); + + // Now make sure we show enough precision to accurate display the + // labels. If this is not done then the user might end up with + // a scale that might actually start with, say 13.5, butdue to rounding + // the scale label will ony show 14. + if( abs(floor($min)-$min) > 0 ) { + + // If the user has set a format then we bail out + if( $this->yscale->ticks->label_formatstr == '' && $this->yscale->ticks->label_dateformatstr == '' ) { + $this->yscale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1; + } + } + } + + if( $this->y2scale != null) { + if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) { + list($min,$max) = $this->GetPlotsYMinMax($this->y2plots); + + $lres = $this->GetLinesYMinMax($this->y2lines); + if( is_array($lres) ) { + list($linmin,$linmax) = $lres ; + $min = min($min,$linmin); + $max = max($max,$linmax); + } + $tres = $this->GetTextsYMinMax(true); + if( is_array($tres) ) { + list($tmin,$tmax) = $tres ; + $min = min($min,$tmin); + $max = max($max,$tmax); + } + $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor); + } + elseif( $this->y2scale->IsSpecified() && + ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) { + // The tick calculation will use the user suplied min/max values to determine + // the ticks. If auto_ticks is false the exact user specifed min and max + // values will be used for the scale. + // If auto_ticks is true then the scale might be slightly adjusted + // so that the min and max values falls on an even major step. + $min = $this->y2scale->scale[0]; + $max = $this->y2scale->scale[1]; + $this->y2scale->AutoScale($this->img,$min,$max, + $this->img->plotheight/$this->ytick_factor, + $this->y2scale->auto_ticks); + + // Now make sure we show enough precision to accurate display the + // labels. If this is not done then the user might end up with + // a scale that might actually start with, say 13.5, butdue to rounding + // the scale label will ony show 14. + if( abs(floor($min)-$min) > 0 ) { + + // If the user has set a format then we bail out + if( $this->y2scale->ticks->label_formatstr == '' && $this->y2scale->ticks->label_dateformatstr == '' ) { + $this->y2scale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1; + } + } + + } + } + + // + // Autoscale the extra Y-axises + // + $n = count($this->ynaxis); + for( $i=0; $i < $n; ++$i ) { + if( $this->ynscale[$i] != null) { + if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) { + list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]); + $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor); + } + elseif( $this->ynscale[$i]->IsSpecified() && + ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) { + // The tick calculation will use the user suplied min/max values to determine + // the ticks. If auto_ticks is false the exact user specifed min and max + // values will be used for the scale. + // If auto_ticks is true then the scale might be slightly adjusted + // so that the min and max values falls on an even major step. + $min = $this->ynscale[$i]->scale[0]; + $max = $this->ynscale[$i]->scale[1]; + $this->ynscale[$i]->AutoScale($this->img,$min,$max, + $this->img->plotheight/$this->ytick_factor, + $this->ynscale[$i]->auto_ticks); + + // Now make sure we show enough precision to accurate display the + // labels. If this is not done then the user might end up with + // a scale that might actually start with, say 13.5, butdue to rounding + // the scale label will ony show 14. + if( abs(floor($min)-$min) > 0 ) { + + // If the user has set a format then we bail out + if( $this->ynscale[$i]->ticks->label_formatstr == '' && $this->ynscale[$i]->ticks->label_dateformatstr == '' ) { + $this->ynscale[$i]->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1; + } + } + + } + } + } + + //Check if we should autoscale x-axis + if( !$this->xscale->IsSpecified() ) { + if( substr($this->axtype,0,4) == "text" ) { + $max=0; + $n = count($this->plots); + for($i=0; $i < $n; ++$i ) { + $p = $this->plots[$i]; + // We need some unfortunate sub class knowledge here in order + // to increase number of data points in case it is a line plot + // which has the barcenter set. If not it could mean that the + // last point of the data is outside the scale since the barcenter + // settings means that we will shift the entire plot half a tick step + // to the right in oder to align with the center of the bars. + if( class_exists('BarPlot',false) ) { + $cl = strtolower(get_class($p)); + if( (class_exists('BarPlot',false) && ($p instanceof BarPlot)) || + empty($p->barcenter) ) + $max=max($max,$p->numpoints-1); + else { + $max=max($max,$p->numpoints); + } + } + else { + if( empty($p->barcenter) ) { + $max=max($max,$p->numpoints-1); + } + else { + $max=max($max,$p->numpoints); + } + } + } + $min=0; + if( $this->y2axis != null ) { + foreach( $this->y2plots as $p ) { + $max=max($max,$p->numpoints-1); + } + } + $n = count($this->ynaxis); + for( $i=0; $i < $n; ++$i ) { + if( $this->ynaxis[$i] != null) { + foreach( $this->ynplots[$i] as $p ) { + $max=max($max,$p->numpoints-1); + } + } + } + + $this->xscale->Update($this->img,$min,$max); + $this->xscale->ticks->Set($this->xaxis->tick_step,1); + $this->xscale->ticks->SupressMinorTickMarks(); + } + else { + list($min,$max) = $this->GetXMinMax(); + + $lres = $this->GetLinesXMinMax($this->lines); + if( $lres ) { + list($linmin,$linmax) = $lres ; + $min = min($min,$linmin); + $max = max($max,$linmax); + } + + $lres = $this->GetLinesXMinMax($this->y2lines); + if( $lres ) { + list($linmin,$linmax) = $lres ; + $min = min($min,$linmin); + $max = max($max,$linmax); + } + + $tres = $this->GetTextsXMinMax(); + if( $tres ) { + list($tmin,$tmax) = $tres ; + $min = min($min,$tmin); + $max = max($max,$tmax); + } + + $tres = $this->GetTextsXMinMax(true); + if( $tres ) { + list($tmin,$tmax) = $tres ; + $min = min($min,$tmin); + $max = max($max,$tmax); + } + + $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor)); + } + + //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale + if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) ) + $this->yaxis->SetPos($this->xscale->GetMinVal()); + if( $this->y2axis != null ) { + if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) ) + $this->y2axis->SetPos($this->xscale->GetMaxVal()); + $this->y2axis->SetTitleSide(SIDE_RIGHT); + } + $n = count($this->ynaxis); + $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0; + for( $i=0; $i < $n; ++$i ) { + if( $this->ynaxis[$i] != null ) { + if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) { + $this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal()); + $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj); + } + $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT); + } + } + + } + elseif( $this->xscale->IsSpecified() && + ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) { + // The tick calculation will use the user suplied min/max values to determine + // the ticks. If auto_ticks is false the exact user specifed min and max + // values will be used for the scale. + // If auto_ticks is true then the scale might be slightly adjusted + // so that the min and max values falls on an even major step. + $min = $this->xscale->scale[0]; + $max = $this->xscale->scale[1]; + $this->xscale->AutoScale($this->img,$min,$max, + round($this->img->plotwidth/$this->xtick_factor), + false); + + // Now make sure we show enough precision to accurate display the + // labels. If this is not done then the user might end up with + // a scale that might actually start with, say 13.5, butdue to rounding + // the scale label will ony show 14. + if( abs(floor($min)-$min) > 0 ) { + + // If the user has set a format then we bail out + if( $this->xscale->ticks->label_formatstr == '' && $this->xscale->ticks->label_dateformatstr == '' ) { + $this->xscale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1; + } + } + + + if( $this->y2axis != null ) { + if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) ) + $this->y2axis->SetPos($this->xscale->GetMaxVal()); + $this->y2axis->SetTitleSide(SIDE_RIGHT); + } + + } + + // If we have a negative values and x-axis position is at 0 + // we need to supress the first and possible the last tick since + // they will be drawn on top of the y-axis (and possible y2 axis) + // The test below might seem strange the reasone being that if + // the user hasn't specified a value for position this will not + // be set until we do the stroke for the axis so as of now it + // is undefined. + // For X-text scale we ignore all this since the tick are usually + // much further in and not close to the Y-axis. Hence the test + // for 'text' + + if( ($this->yaxis->pos==$this->xscale->GetMinVal() || + (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) && + !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 && + substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) { + + //$this->yscale->ticks->SupressZeroLabel(false); + $this->xscale->ticks->SupressFirst(); + if( $this->y2axis != null ) { + $this->xscale->ticks->SupressLast(); + } + } + elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) { + $this->xscale->ticks->SupressLast(); + } + + + if( !$_csim ) { + $this->StrokePlotArea(); + if( $this->iIconDepth == DEPTH_BACK ) { + $this->StrokeIcons(); + } + } + $this->StrokeAxis(false); + + // Stroke bands + if( $this->bands != null && !$_csim) + for($i=0; $i < count($this->bands); ++$i) { + // Stroke all bands that asks to be in the background + if( $this->bands[$i]->depth == DEPTH_BACK ) + $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale); + } + + if( $this->y2bands != null && $this->y2scale != null && !$_csim ) + for($i=0; $i < count($this->y2bands); ++$i) { + // Stroke all bands that asks to be in the foreground + if( $this->y2bands[$i]->depth == DEPTH_BACK ) + $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale); + } + + + if( $this->grid_depth == DEPTH_BACK && !$_csim) { + $this->ygrid->Stroke(); + $this->xgrid->Stroke(); + } + + // Stroke Y2-axis + if( $this->y2axis != null && !$_csim) { + $this->y2axis->Stroke($this->xscale); + $this->y2grid->Stroke(); + } + + // Stroke yn-axis + $n = count($this->ynaxis); + for( $i=0; $i < $n; ++$i ) { + $this->ynaxis[$i]->Stroke($this->xscale); + } + + $oldoff=$this->xscale->off; + if(substr($this->axtype,0,4)=="text") { + if( $this->text_scale_abscenteroff > -1 ) { + // For a text scale the scale factor is the number of pixel per step. + // Hence we can use the scale factor as a substitute for number of pixels + // per major scale step and use that in order to adjust the offset so that + // an object of width "abscenteroff" becomes centered. + $this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2); + } + else { + $this->xscale->off += + ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step); + } + } + + if( $this->iDoClipping ) { + $oldimage = $this->img->CloneCanvasH(); + } + + if( ! $this->y2orderback ) { + // Stroke all plots for Y1 axis + for($i=0; $i < count($this->plots); ++$i) { + $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale); + $this->plots[$i]->StrokeMargin($this->img); + } + } + + // Stroke all plots for Y2 axis + if( $this->y2scale != null ) + for($i=0; $i< count($this->y2plots); ++$i ) { + $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale); + } + + if( $this->y2orderback ) { + // Stroke all plots for Y1 axis + for($i=0; $i < count($this->plots); ++$i) { + $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale); + $this->plots[$i]->StrokeMargin($this->img); + } + } + + $n = count($this->ynaxis); + for( $i=0; $i < $n; ++$i ) { + $m = count($this->ynplots[$i]); + for( $j=0; $j < $m; ++$j ) { + $this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]); + $this->ynplots[$i][$j]->StrokeMargin($this->img); + } + } + + if( $this->iIconDepth == DEPTH_FRONT) { + $this->StrokeIcons(); + } + + if( $this->iDoClipping ) { + // Clipping only supports graphs at 0 and 90 degrees + if( $this->img->a == 0 ) { + $this->img->CopyCanvasH($oldimage,$this->img->img, + $this->img->left_margin,$this->img->top_margin, + $this->img->left_margin,$this->img->top_margin, + $this->img->plotwidth+1,$this->img->plotheight); + } + elseif( $this->img->a == 90 ) { + $adj = ($this->img->height - $this->img->width)/2; + $this->img->CopyCanvasH($oldimage,$this->img->img, + $this->img->bottom_margin-$adj,$this->img->left_margin+$adj, + $this->img->bottom_margin-$adj,$this->img->left_margin+$adj, + $this->img->plotheight+1,$this->img->plotwidth); + } + else { + JpGraphError::RaiseL(25035,$this->img->a);//('You have enabled clipping. Cliping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (='.$this->img->a.' degrees) or disable clipping.'); + } + $this->img->Destroy(); + $this->img->SetCanvasH($oldimage); + } + + $this->xscale->off=$oldoff; + + if( $this->grid_depth == DEPTH_FRONT && !$_csim ) { + $this->ygrid->Stroke(); + $this->xgrid->Stroke(); + } + + // Stroke bands + if( $this->bands!= null ) + for($i=0; $i < count($this->bands); ++$i) { + // Stroke all bands that asks to be in the foreground + if( $this->bands[$i]->depth == DEPTH_FRONT ) + $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale); + } + + if( $this->y2bands!= null && $this->y2scale != null ) + for($i=0; $i < count($this->y2bands); ++$i) { + // Stroke all bands that asks to be in the foreground + if( $this->y2bands[$i]->depth == DEPTH_FRONT ) + $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale); + } + + + // Stroke any lines added + if( $this->lines != null ) { + for($i=0; $i < count($this->lines); ++$i) { + $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale); + $this->lines[$i]->DoLegend($this); + } + } + + if( $this->y2lines != null && $this->y2scale != null ) { + for($i=0; $i < count($this->y2lines); ++$i) { + $this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale); + $this->y2lines[$i]->DoLegend($this); + } + } + + // Finally draw the axis again since some plots may have nagged + // the axis in the edges. + if( !$_csim ) { + $this->StrokeAxis(); + } + + if( $this->y2scale != null && !$_csim ) + $this->y2axis->Stroke($this->xscale,false); + + if( !$_csim ) { + $this->StrokePlotBox(); + } + + // The titles and legends never gets rotated so make sure + // that the angle is 0 before stroking them + $aa = $this->img->SetAngle(0); + $this->StrokeTitles(); + $this->footer->Stroke($this->img); + $this->legend->Stroke($this->img); + $this->img->SetAngle($aa); + $this->StrokeTexts(); + $this->StrokeTables(); + + if( !$_csim ) { + + $this->img->SetAngle($aa); + + // Draw an outline around the image map + if(_JPG_DEBUG) { + $this->DisplayClientSideaImageMapAreas(); + } + + // Should we do any final image transformation + if( $this->iImgTrans ) { + if( !class_exists('ImgTrans',false) ) { + require_once('jpgraph_imgtrans.php'); + //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.'); + } + + $tform = new ImgTrans($this->img->img); + $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist, + $this->iImgTransDirection,$this->iImgTransHighQ, + $this->iImgTransMinSize,$this->iImgTransFillColor, + $this->iImgTransBorder); + } + + // If the filename is given as the special "__handle" + // then the image handler is returned and the image is NOT + // streamed back + if( $aStrokeFileName == _IMG_HANDLER ) { + return $this->img->img; + } + else { + // Finally stream the generated picture + $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName); + } + } + } + + function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') { + $this->iAxisLblBgType = $aType; + $this->iXAxisLblBgFillColor = $aXFColor; + $this->iXAxisLblBgColor = $aXColor; + $this->iYAxisLblBgFillColor = $aYFColor; + $this->iYAxisLblBgColor = $aYColor; + } + +//--------------- +// PRIVATE METHODS + + function StrokeAxisLabelBackground() { + // Types + // 0 = No background + // 1 = Only X-labels, length of axis + // 2 = Only Y-labels, length of axis + // 3 = As 1 but extends to width of graph + // 4 = As 2 but extends to height of graph + // 5 = Combination of 3 & 4 + // 6 = Combination of 1 & 2 + + $t = $this->iAxisLblBgType ; + if( $t < 1 ) return; + // Stroke optional X-axis label background color + if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) { + $this->img->PushColor($this->iXAxisLblBgFillColor); + if( $t == 1 || $t == 6 ) { + $xl = $this->img->left_margin; + $yu = $this->img->height - $this->img->bottom_margin + 1; + $xr = $this->img->width - $this->img->right_margin ; + $yl = $this->img->height-1-$this->frame_weight; + } + else { // t==3 || t==5 + $xl = $this->frame_weight; + $yu = $this->img->height - $this->img->bottom_margin + 1; + $xr = $this->img->width - 1 - $this->frame_weight; + $yl = $this->img->height-1-$this->frame_weight; + } + + $this->img->FilledRectangle($xl,$yu,$xr,$yl); + $this->img->PopColor(); + + // Check if we should add the vertical lines at left and right edge + if( $this->iXAxisLblBgColor !== '' ) { + $this->img->PushColor($this->iXAxisLblBgColor); + if( $t == 1 || $t == 6 ) { + $this->img->Line($xl,$yu,$xl,$yl); + $this->img->Line($xr,$yu,$xr,$yl); + } + else { + $xl = $this->img->width - $this->img->right_margin ; + $this->img->Line($xl,$yu-1,$xr,$yu-1); + } + $this->img->PopColor(); + } + } + + if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) { + $this->img->PushColor($this->iYAxisLblBgFillColor); + if( $t == 2 || $t == 6 ) { + $xl = $this->frame_weight; + $yu = $this->frame_weight+$this->img->top_margin; + $xr = $this->img->left_margin - 1; + $yl = $this->img->height - $this->img->bottom_margin + 1; + } + else { + $xl = $this->frame_weight; + $yu = $this->frame_weight; + $xr = $this->img->left_margin - 1; + $yl = $this->img->height-1-$this->frame_weight; + } + + $this->img->FilledRectangle($xl,$yu,$xr,$yl); + $this->img->PopColor(); + + // Check if we should add the vertical lines at left and right edge + if( $this->iXAxisLblBgColor !== '' ) { + $this->img->PushColor($this->iXAxisLblBgColor); + if( $t == 2 || $t == 6 ) { + $this->img->Line($xl,$yu-1,$xr,$yu-1); + $this->img->Line($xl,$yl-1,$xr,$yl-1); + } + else { + $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin); + } + $this->img->PopColor(); + } + + } + } + + function StrokeAxis($aStrokeLabels=true) { + + if( $aStrokeLabels ) { + $this->StrokeAxisLabelBackground(); + } + + // Stroke axis + if( $this->iAxisStyle != AXSTYLE_SIMPLE ) { + switch( $this->iAxisStyle ) { + case AXSTYLE_BOXIN : + $toppos = SIDE_DOWN; + $bottompos = SIDE_UP; + $leftpos = SIDE_RIGHT; + $rightpos = SIDE_LEFT; + break; + case AXSTYLE_BOXOUT : + $toppos = SIDE_UP; + $bottompos = SIDE_DOWN; + $leftpos = SIDE_LEFT; + $rightpos = SIDE_RIGHT; + break; + case AXSTYLE_YBOXIN: + $toppos = FALSE; + $bottompos = SIDE_UP; + $leftpos = SIDE_RIGHT; + $rightpos = SIDE_LEFT; + break; + case AXSTYLE_YBOXOUT: + $toppos = FALSE; + $bottompos = SIDE_DOWN; + $leftpos = SIDE_LEFT; + $rightpos = SIDE_RIGHT; + break; + default: + JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle); + break; + } + + // By default we hide the first label so it doesn't cross the + // Y-axis in case the positon hasn't been set by the user. + // However, if we use a box we always want the first value + // displayed so we make sure it will be displayed. + $this->xscale->ticks->SupressFirst(false); + + // Now draw the bottom X-axis + $this->xaxis->SetPos('min'); + $this->xaxis->SetLabelSide(SIDE_DOWN); + $this->xaxis->scale->ticks->SetSide($bottompos); + $this->xaxis->Stroke($this->yscale,$aStrokeLabels); + + if( $toppos !== FALSE ) { + // We also want a top X-axis + $this->xaxis = $this->xaxis; + $this->xaxis->SetPos('max'); + $this->xaxis->SetLabelSide(SIDE_UP); + // No title for the top X-axis + if( $aStrokeLabels ) { + $this->xaxis->title->Set(''); + } + $this->xaxis->scale->ticks->SetSide($toppos); + $this->xaxis->Stroke($this->yscale,$aStrokeLabels); + } + + // Stroke the left Y-axis + $this->yaxis->SetPos('min'); + $this->yaxis->SetLabelSide(SIDE_LEFT); + $this->yaxis->scale->ticks->SetSide($leftpos); + $this->yaxis->Stroke($this->xscale,$aStrokeLabels); + + // Stroke the right Y-axis + $this->yaxis->SetPos('max'); + // No title for the right side + if( $aStrokeLabels ) { + $this->yaxis->title->Set(''); + } + $this->yaxis->SetLabelSide(SIDE_RIGHT); + $this->yaxis->scale->ticks->SetSide($rightpos); + $this->yaxis->Stroke($this->xscale,$aStrokeLabels); + } + else { + $this->xaxis->Stroke($this->yscale,$aStrokeLabels); + $this->yaxis->Stroke($this->xscale,$aStrokeLabels); + } + } + + + // Private helper function for backgound image + static function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') { + if( $aImgStr != '' ) { + return Image::CreateFromString($aImgStr); + } + + // Remove case sensitivity and setup appropriate function to create image + // Get file extension. This should be the LAST '.' separated part of the filename + $e = explode('.',$aFile); + $ext = strtolower($e[count($e)-1]); + if ($ext == "jpeg") { + $ext = "jpg"; + } + + if( trim($ext) == '' ) + $ext = 'png'; // Assume PNG if no extension specified + + if( $aImgFormat == '' ) + $imgtag = $ext; + else + $imgtag = $aImgFormat; + + $supported = imagetypes(); + if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) || + ( $ext == 'gif' && !($supported & IMG_GIF) ) || + ( $ext == 'png' && !($supported & IMG_PNG) ) || + ( $ext == 'bmp' && !($supported & IMG_WBMP) ) || + ( $ext == 'xpm' && !($supported & IMG_XPM) ) ) { + + JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. '); + } + + + if( $imgtag == "jpg" || $imgtag == "jpeg") + { + $f = "imagecreatefromjpeg"; + $imgtag = "jpg"; + } + else + { + $f = "imagecreatefrom".$imgtag; + } + + // Compare specified image type and file extension + if( $imgtag != $ext ) { + //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'"; + JpGraphError::RaiseL(25038, $aImgFormat, $aFile); + } + + $img = @$f($aFile); + if( !$img ) { + JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'"); + } + return $img; + } + + function StrokeBackgroundGrad() { + if( $this->bkg_gradtype < 0 ) + return; + $grad = new Gradient($this->img); + if( $this->bkg_gradstyle == BGRAD_PLOT ) { + $xl = $this->img->left_margin; + $yt = $this->img->top_margin; + $xr = $xl + $this->img->plotwidth+1 ; + $yb = $yt + $this->img->plotheight ; + $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype); + } + else { + $xl = 0; + $yt = 0; + $xr = $xl + $this->img->width - 1; + $yb = $yt + $this->img->height ; + if( $this->doshadow ) { + $xr -= $this->shadow_width; + $yb -= $this->shadow_width; + } + if( $this->doframe ) { + $yt += $this->frame_weight; + $yb -= $this->frame_weight; + $xl += $this->frame_weight; + $xr -= $this->frame_weight; + } + $aa = $this->img->SetAngle(0); + $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype); + $aa = $this->img->SetAngle($aa); + } + } + + function StrokeFrameBackground() { + if( $this->background_image != "" && $this->background_cflag != "" ) { + JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.'); + } + if( $this->background_image != "" ) { + $bkgimg = $this->LoadBkgImage($this->background_image_format,$this->background_image); + } + elseif( $this->background_cflag != "" ) { + if( ! class_exists('FlagImages',false) ) { + JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.'); + } + $fobj = new FlagImages(FLAGSIZE4); + $dummy=''; + $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy); + $this->background_image_mix = $this->background_cflag_mix; + $this->background_image_type = $this->background_cflag_type; + } + else { + return ; + } + + $bw = ImageSX($bkgimg); + $bh = ImageSY($bkgimg); + + // No matter what the angle is we always stroke the image and frame + // assuming it is 0 degree + $aa = $this->img->SetAngle(0); + + switch( $this->background_image_type ) { + case BGIMG_FILLPLOT: // Resize to just fill the plotarea + $this->FillMarginArea(); + $this->StrokeFrame(); + // Special case to hande 90 degree rotated graph corectly + if( $aa == 90 ) { + $this->img->SetAngle(90); + $this->FillPlotArea(); + $aa = $this->img->SetAngle(0); + $adj = ($this->img->height - $this->img->width)/2; + $this->img->CopyMerge($bkgimg, + $this->img->bottom_margin-$adj,$this->img->left_margin+$adj, + 0,0, + $this->img->plotheight+1,$this->img->plotwidth, + $bw,$bh,$this->background_image_mix); + + } + else { + $this->FillPlotArea(); + $this->img->CopyMerge($bkgimg, + $this->img->left_margin,$this->img->top_margin, + 0,0,$this->img->plotwidth+1,$this->img->plotheight, + $bw,$bh,$this->background_image_mix); + } + break; + case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit + $hadj=0; $vadj=0; + if( $this->doshadow ) { + $hadj = $this->shadow_width; + $vadj = $this->shadow_width; + } + $this->FillMarginArea(); + $this->FillPlotArea(); + $this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj, + $bw,$bh,$this->background_image_mix); + $this->StrokeFrame(); + break; + case BGIMG_COPY: // Just copy the image from left corner, no resizing + $this->FillMarginArea(); + $this->FillPlotArea(); + $this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh, + $bw,$bh,$this->background_image_mix); + $this->StrokeFrame(); + break; + case BGIMG_CENTER: // Center original image in the plot area + $this->FillMarginArea(); + $this->FillPlotArea(); + $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2); + $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2); + $this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh, + $bw,$bh,$this->background_image_mix); + $this->StrokeFrame(); + break; + default: + JpGraphError::RaiseL(25042);//(" Unknown background image layout"); + } + $this->img->SetAngle($aa); + } + + // Private + // Draw a frame around the image + function StrokeFrame() { + if( !$this->doframe ) return; + + if( $this->background_image_type <= 1 && + ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) { + $c = $this->margin_color; + } + else { + $c = false; + } + + if( $this->doshadow ) { + $this->img->SetColor($this->frame_color); + $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height, + $c,$this->shadow_width,$this->shadow_color); + } + elseif( $this->framebevel ) { + if( $c ) { + $this->img->SetColor($this->margin_color); + $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); + } + $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2, + $this->framebeveldepth, + $this->framebevelcolor1,$this->framebevelcolor2); + if( $this->framebevelborder ) { + $this->img->SetColor($this->framebevelbordercolor); + $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1); + } + } + else { + $this->img->SetLineWeight($this->frame_weight); + if( $c ) { + $this->img->SetColor($this->margin_color); + $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); + } + $this->img->SetColor($this->frame_color); + $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1); + } + } + + function FillMarginArea() { + $hadj=0; $vadj=0; + if( $this->doshadow ) { + $hadj = $this->shadow_width; + $vadj = $this->shadow_width; + } + + $this->img->SetColor($this->margin_color); +// $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj); + + $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin); + $this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj); + $this->img->FilledRectangle($this->img->left_margin+1, + $this->img->height-$this->img->bottom_margin, + $this->img->width-1-$hadj, + $this->img->height-1-$hadj); + $this->img->FilledRectangle($this->img->width-$this->img->right_margin, + $this->img->top_margin+1, + $this->img->width-1-$hadj, + $this->img->height-$this->img->bottom_margin-1); + } + + function FillPlotArea() { + $this->img->PushColor($this->plotarea_color); + $this->img->FilledRectangle($this->img->left_margin, + $this->img->top_margin, + $this->img->width-$this->img->right_margin, + $this->img->height-$this->img->bottom_margin); + $this->img->PopColor(); + } + + // Stroke the plot area with either a solid color or a background image + function StrokePlotArea() { + // Note: To be consistent we really should take a possible shadow + // into account. However, that causes some problem for the LinearScale class + // since in the current design it does not have any links to class Graph which + // means it has no way of compensating for the adjusted plotarea in case of a + // shadow. So, until I redesign LinearScale we can't compensate for this. + // So just set the two adjustment parameters to zero for now. + $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ; + $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ; + + if( $this->background_image != "" || $this->background_cflag != "" ) { + $this->StrokeFrameBackground(); + } + else { + $aa = $this->img->SetAngle(0); + $this->StrokeFrame(); + $aa = $this->img->SetAngle($aa); + $this->StrokeBackgroundGrad(); + if( $this->bkg_gradtype < 0 || + ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) { + $this->FillPlotArea(); + } + } + } + + function StrokeIcons() { + $n = count($this->iIcons); + for( $i=0; $i < $n; ++$i ) { + $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale); + } + } + + function StrokePlotBox() { + // Should we draw a box around the plot area? + if( $this->boxed ) { + $this->img->SetLineWeight(1); + $this->img->SetLineStyle('solid'); + $this->img->SetColor($this->box_color); + for($i=0; $i < $this->box_weight; ++$i ) { + $this->img->Rectangle( + $this->img->left_margin-$i,$this->img->top_margin-$i, + $this->img->width-$this->img->right_margin+$i, + $this->img->height-$this->img->bottom_margin+$i); + } + } + } + + function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') { + $this->titlebkg_fillstyle = $aStyle; + $this->titlebkg_scolor1 = $aColor1; + $this->titlebkg_scolor2 = $aColor2; + } + + function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) { + $this->titlebackground = $aEnable; + $this->titlebackground_color = $aBackColor; + $this->titlebackground_style = $aStyle; + $this->titlebackground_framecolor = $aFrameColor; + $this->titlebackground_framestyle = $aFrameStyle; + $this->titlebackground_frameweight = $aFrameWeight; + $this->titlebackground_bevelheight = $aBevelHeight ; + } + + + function StrokeTitles() { + + $margin=3; + + if( $this->titlebackground ) { + + // Find out height + $this->title->margin += 2 ; + $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin; + if( $this->subtitle->t != "" && !$this->subtitle->hide ) { + $h += $this->subtitle->GetTextHeight($this->img)+$margin+ + $this->subtitle->margin; + $h += 2; + } + if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) { + $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+ + $this->subsubtitle->margin; + $h += 2; + } + $this->img->PushColor($this->titlebackground_color); + if( $this->titlebackground_style === TITLEBKG_STYLE1 ) { + // Inside the frame + if( $this->framebevel ) { + $x1 = $y1 = $this->framebeveldepth + 1 ; + $x2 = $this->img->width - $this->framebeveldepth - 2 ; + $this->title->margin += $this->framebeveldepth + 1 ; + $h += $y1 ; + $h += 2; + } + else { + $x1 = $y1 = $this->frame_weight; + $x2 = $this->img->width - 2*$x1; + } + } + elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) { + // Cover the frame as well + $x1 = $y1 = 0; + $x2 = $this->img->width - 1 ; + } + elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) { + // Cover the frame as well (the difference is that + // for style==3 a bevel frame border is on top + // of the title background) + $x1 = $y1 = 0; + $x2 = $this->img->width - 1 ; + $h += $this->framebeveldepth ; + $this->title->margin += $this->framebeveldepth ; + } + else { + JpGraphError::RaiseL(25043);//('Unknown title background style.'); + } + + if( $this->titlebackground_framestyle === 3 ) { + $h += $this->titlebackground_bevelheight*2 + 1 ; + $this->title->margin += $this->titlebackground_bevelheight ; + } + + if( $this->doshadow ) { + $x2 -= $this->shadow_width ; + } + + $indent=0; + if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) { + $ind = $this->titlebackground_bevelheight; + } + + if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) { + $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind, + $this->titlebkg_scolor1, + $this->titlebkg_scolor2); + } + elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) { + $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind, + $this->titlebkg_scolor1, + $this->titlebkg_scolor2,2); + } + else { + // Solid fill + $this->img->FilledRectangle($x1,$y1,$x2,$h); + } + $this->img->PopColor(); + + $this->img->PushColor($this->titlebackground_framecolor); + $this->img->SetLineWeight($this->titlebackground_frameweight); + if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) { + // Frame background + $this->img->Rectangle($x1,$y1,$x2,$h); + } + elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) { + // Bottom line only + $this->img->Line($x1,$h,$x2,$h); + } + elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) { + $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight); + } + $this->img->PopColor(); + + // This is clumsy. But we neeed to stroke the whole graph frame if it is + // set to bevel to get the bevel shading on top of the text background + if( $this->framebevel && $this->doframe && + $this->titlebackground_style === 3 ) { + $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2, + $this->framebeveldepth, + $this->framebevelcolor1,$this->framebevelcolor2); + if( $this->framebevelborder ) { + $this->img->SetColor($this->framebevelbordercolor); + $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1); + } + } + } + + // Stroke title + $y = $this->title->margin; + if( $this->title->halign == 'center' ) + $this->title->Center(0,$this->img->width,$y); + elseif( $this->title->halign == 'left' ) { + $this->title->SetPos($this->title->margin+2,$y); + } + elseif( $this->title->halign == 'right' ) { + $indent = 0; + if( $this->doshadow ) + $indent = $this->shadow_width+2; + $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right'); + } + $this->title->Stroke($this->img); + + // ... and subtitle + $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin; + if( $this->subtitle->halign == 'center' ) + $this->subtitle->Center(0,$this->img->width,$y); + elseif( $this->subtitle->halign == 'left' ) { + $this->subtitle->SetPos($this->subtitle->margin+2,$y); + } + elseif( $this->subtitle->halign == 'right' ) { + $indent = 0; + if( $this->doshadow ) + $indent = $this->shadow_width+2; + $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right'); + } + $this->subtitle->Stroke($this->img); + + // ... and subsubtitle + $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin; + if( $this->subsubtitle->halign == 'center' ) + $this->subsubtitle->Center(0,$this->img->width,$y); + elseif( $this->subsubtitle->halign == 'left' ) { + $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y); + } + elseif( $this->subsubtitle->halign == 'right' ) { + $indent = 0; + if( $this->doshadow ) + $indent = $this->shadow_width+2; + $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right'); + } + $this->subsubtitle->Stroke($this->img); + + // ... and fancy title + $this->tabtitle->Stroke($this->img); + + } + + function StrokeTexts() { + // Stroke any user added text objects + if( $this->texts != null ) { + for($i=0; $i < count($this->texts); ++$i) { + $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale); + } + } + + if( $this->y2texts != null && $this->y2scale != null ) { + for($i=0; $i < count($this->y2texts); ++$i) { + $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale); + } + } + + } + + function StrokeTables() { + if( $this->iTables != null ) { + $n = count($this->iTables); + for( $i=0; $i < $n; ++$i ) { + $this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale); + } + } + } + + function DisplayClientSideaImageMapAreas() { + // Debug stuff - display the outline of the image map areas + $csim=''; + foreach ($this->plots as $p) { + $csim.= $p->GetCSIMareas(); + } + $csim .= $this->legend->GetCSIMareas(); + if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) { + $this->img->SetColor($this->csimcolor); + $n = count($coords[0]); + for ($i=0; $i < $n; $i++) { + if ($coords[1][$i]=="poly") { + preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts); + $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]); + $m = count($pts[0]); + for ($j=0; $j < $m; $j++) { + $this->img->LineTo($pts[1][$j],$pts[2][$j]); + } + } else if ($coords[1][$i]=="rect") { + $pts = preg_split('/,/', $coords[2][$i]); + $this->img->SetStartPoint($pts[0],$pts[1]); + $this->img->LineTo($pts[2],$pts[1]); + $this->img->LineTo($pts[2],$pts[3]); + $this->img->LineTo($pts[0],$pts[3]); + $this->img->LineTo($pts[0],$pts[1]); + } + } + } + } + + // Text scale offset in world coordinates + function SetTextScaleOff($aOff) { + $this->text_scale_off = $aOff; + $this->xscale->text_scale_off = $aOff; + } + + // Text width of bar to be centered in absolute pixels + function SetTextScaleAbsCenterOff($aOff) { + $this->text_scale_abscenteroff = $aOff; + } + + // Get Y min and max values for added lines + function GetLinesYMinMax( $aLines ) { + $n = count($aLines); + if( $n == 0 ) return false; + $min = $aLines[0]->scaleposition ; + $max = $min ; + $flg = false; + for( $i=0; $i < $n; ++$i ) { + if( $aLines[$i]->direction == HORIZONTAL ) { + $flg = true ; + $v = $aLines[$i]->scaleposition ; + if( $min > $v ) $min = $v ; + if( $max < $v ) $max = $v ; + } + } + return $flg ? array($min,$max) : false ; + } + + // Get X min and max values for added lines + function GetLinesXMinMax( $aLines ) { + $n = count($aLines); + if( $n == 0 ) return false ; + $min = $aLines[0]->scaleposition ; + $max = $min ; + $flg = false; + for( $i=0; $i < $n; ++$i ) { + if( $aLines[$i]->direction == VERTICAL ) { + $flg = true ; + $v = $aLines[$i]->scaleposition ; + if( $min > $v ) $min = $v ; + if( $max < $v ) $max = $v ; + } + } + return $flg ? array($min,$max) : false ; + } + + // Get min and max values for all included plots + function GetPlotsYMinMax($aPlots) { + $n = count($aPlots); + $i=0; + do { + list($xmax,$max) = $aPlots[$i]->Max(); + } while( ++$i < $n && !is_numeric($max) ); + + $i=0; + do { + list($xmin,$min) = $aPlots[$i]->Min(); + } while( ++$i < $n && !is_numeric($min) ); + + if( !is_numeric($min) || !is_numeric($max) ) { + JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).'); + } + + for($i=0; $i < $n; ++$i ) { + list($xmax,$ymax)=$aPlots[$i]->Max(); + list($xmin,$ymin)=$aPlots[$i]->Min(); + if (is_numeric($ymax)) $max=max($max,$ymax); + if (is_numeric($ymin)) $min=min($min,$ymin); + } + if( $min == '' ) $min = 0; + if( $max == '' ) $max = 0; + if( $min == 0 && $max == 0 ) { + // Special case if all values are 0 + $min=0;$max=1; + } + return array($min,$max); + } + +} // Class + +//=================================================== +// CLASS LineProperty +// Description: Holds properties for a line +//=================================================== +class LineProperty { + public $iWeight=1, $iColor="black",$iStyle="solid",$iShow=true; + +//--------------- +// PUBLIC METHODS + function SetColor($aColor) { + $this->iColor = $aColor; + } + + function SetWeight($aWeight) { + $this->iWeight = $aWeight; + } + + function SetStyle($aStyle) { + $this->iStyle = $aStyle; + } + + function Show($aShow=true) { + $this->iShow=$aShow; + } + + function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) { + if( $this->iShow ) { + $aImg->PushColor($this->iColor); + $oldls = $aImg->line_style; + $oldlw = $aImg->line_weight; + $aImg->SetLineWeight($this->iWeight); + $aImg->SetLineStyle($this->iStyle); + $aImg->StyleLine($aX1,$aY1,$aX2,$aY2); + $aImg->PopColor($this->iColor); + $aImg->line_style = $oldls; + $aImg->line_weight = $oldlw; + + } + } +} + + +//=================================================== +// CLASS Text +// Description: Arbitrary text object that can be added to the graph +//=================================================== +class Text { + public $t,$margin=0; + public $x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0); + public $hide=false, $dir=0; + public $iScalePosY=null,$iScalePosX=null; + public $iWordwrap=0; + public $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12; + protected $boxed=false; // Should the text be boxed + protected $paragraph_align="left"; + protected $icornerradius=0,$ishadowwidth=3; + protected $fcolor='white',$bcolor='black',$shadow=false; + protected $iCSIMarea='',$iCSIMalt='',$iCSIMtarget='',$iCSIMWinTarget=''; + +//--------------- +// CONSTRUCTOR + + // Create new text at absolute pixel coordinates + function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) { + if( ! is_string($aTxt) ) { + JpGraphError::RaiseL(25050);//('First argument to Text::Text() must be s atring.'); + } + $this->t = $aTxt; + $this->x = round($aXAbsPos); + $this->y = round($aYAbsPos); + $this->margin = 0; + } +//--------------- +// PUBLIC METHODS + // Set the string in the text object + function Set($aTxt) { + $this->t = $aTxt; + } + + // Alias for Pos() + function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") { + //$this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign); + $this->x = $aXAbsPos; + $this->y = $aYAbsPos; + $this->halign = $aHAlign; + $this->valign = $aVAlign; + } + + function SetScalePos($aX,$aY) { + $this->iScalePosX = $aX; + $this->iScalePosY = $aY; + } + + // Specify alignment for the text + function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") { + $this->halign = $aHAlign; + $this->valign = $aVAlign; + if( $aParagraphAlign != "" ) + $this->paragraph_align = $aParagraphAlign; + } + + // Alias + function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") { + $this->Align($aHAlign,$aVAlign,$aParagraphAlign); + } + + // Specifies the alignment for a multi line text + function ParagraphAlign($aAlign) { + $this->paragraph_align = $aAlign; + } + + // Specifies the alignment for a multi line text + function SetParagraphAlign($aAlign) { + $this->paragraph_align = $aAlign; + } + + function SetShadow($aShadowColor='gray',$aShadowWidth=3) { + $this->ishadowwidth=$aShadowWidth; + $this->shadow=$aShadowColor; + $this->boxed=true; + } + + function SetWordWrap($aCol) { + $this->iWordwrap = $aCol ; + } + + // Specify that the text should be boxed. fcolor=frame color, bcolor=border color, + // $shadow=drop shadow should be added around the text. + function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) { + if( $aFrameColor==false ) + $this->boxed=false; + else + $this->boxed=true; + $this->fcolor=$aFrameColor; + $this->bcolor=$aBorderColor; + // For backwards compatibility when shadow was just true or false + if( $aShadowColor === true ) + $aShadowColor = 'gray'; + $this->shadow=$aShadowColor; + $this->icornerradius=$aCornerRadius; + $this->ishadowwidth=$aShadowWidth; + } + + // Hide the text + function Hide($aHide=true) { + $this->hide=$aHide; + } + + // This looks ugly since it's not a very orthogonal design + // but I added this "inverse" of Hide() to harmonize + // with some classes which I designed more recently (especially) + // jpgraph_gantt + function Show($aShow=true) { + $this->hide=!$aShow; + } + + // Specify font + function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) { + $this->font_family=$aFamily; + $this->font_style=$aStyle; + $this->font_size=$aSize; + } + + // Center the text between $left and $right coordinates + function Center($aLeft,$aRight,$aYAbsPos=false) { + $this->x = $aLeft + ($aRight-$aLeft )/2; + $this->halign = "center"; + if( is_numeric($aYAbsPos) ) + $this->y = $aYAbsPos; + } + + // Set text color + function SetColor($aColor) { + $this->color = $aColor; + } + + function SetAngle($aAngle) { + $this->SetOrientation($aAngle); + } + + // Orientation of text. Note only TTF fonts can have an arbitrary angle + function SetOrientation($aDirection=0) { + if( is_numeric($aDirection) ) + $this->dir=$aDirection; + elseif( $aDirection=="h" ) + $this->dir = 0; + elseif( $aDirection=="v" ) + $this->dir = 90; + else JpGraphError::RaiseL(25051);//(" Invalid direction specified for text."); + } + + // Total width of text + function GetWidth($aImg) { + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $w = $aImg->GetTextWidth($this->t,$this->dir); + return $w; + } + + // Hight of font + function GetFontHeight($aImg) { + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $h = $aImg->GetFontHeight(); + return $h; + + } + + function GetTextHeight($aImg) { + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $h = $aImg->GetTextHeight($this->t,$this->dir); + return $h; + } + + function GetHeight($aImg) { + // Synonym for GetTextHeight() + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $h = $aImg->GetTextHeight($this->t,$this->dir); + return $h; + } + + // Set the margin which will be interpretated differently depending + // on the context. + function SetMargin($aMarg) { + $this->margin = $aMarg; + } + + function StrokeWithScale($aImg,$axscale,$ayscale) { + if( $this->iScalePosX === null || + $this->iScalePosY === null ) { + $this->Stroke($aImg); + } + else { + $this->Stroke($aImg, + round($axscale->Translate($this->iScalePosX)), + round($ayscale->Translate($this->iScalePosY))); + } + } + + function SetCSIMTarget($aURITarget,$aAlt='',$aWinTarget='') { + $this->iCSIMtarget = $aURITarget; + $this->iCSIMalt = $aAlt; + $this->iCSIMWinTarget = $aWinTarget; + } + + function GetCSIMareas() { + if( $this->iCSIMtarget !== '' ) + return $this->iCSIMarea; + else + return ''; + } + + // Display text in image + function Stroke($aImg,$x=null,$y=null) { + + if( !empty($x) ) $this->x = round($x); + if( !empty($y) ) $this->y = round($y); + + // Insert newlines + if( $this->iWordwrap > 0 ) { + $this->t = wordwrap($this->t,$this->iWordwrap,"\n"); + } + + // If position been given as a fraction of the image size + // calculate the absolute position + if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width; + if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height; + + $aImg->PushColor($this->color); + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $aImg->SetTextAlign($this->halign,$this->valign); + if( $this->boxed ) { + if( $this->fcolor=="nofill" ) + $this->fcolor=false; + $aImg->SetLineWeight(1); + $bbox = $aImg->StrokeBoxedText($this->x,$this->y,$this->t, + $this->dir,$this->fcolor,$this->bcolor,$this->shadow, + $this->paragraph_align,5,5,$this->icornerradius, + $this->ishadowwidth); + } + else { + $bbox = $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,$this->paragraph_align); + } + + // Create CSIM targets + $coords = $bbox[0].','.$bbox[1].','.$bbox[2].','.$bbox[3].','.$bbox[4].','.$bbox[5].','.$bbox[6].','.$bbox[7]; + $this->iCSIMarea = "iCSIMtarget)."\" "; + if( trim($this->iCSIMalt) != '' ) { + $this->iCSIMarea .= " alt=\"".$this->iCSIMalt."\" "; + $this->iCSIMarea .= " title=\"".$this->iCSIMalt."\" "; + } + if( trim($this->iCSIMWinTarget) != '' ) { + $this->iCSIMarea .= " target=\"".$this->iCSIMWinTarget."\" "; + } + $this->iCSIMarea .= " />\n"; + + $aImg->PopColor($this->color); + + } +} // Class + +class GraphTabTitle extends Text{ + private $corner = 6 , $posx = 7, $posy = 4; + private $fillcolor='lightyellow',$bordercolor='black'; + private $align = 'left', $width=TABTITLE_WIDTHFIT; + function GraphTabTitle() { + $this->t = ''; + $this->font_style = FS_BOLD; + $this->hide = true; + $this->color = 'darkred'; + } + + function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') { + $this->color = $aTxtColor; + $this->fillcolor = $aFillColor; + $this->bordercolor = $aBorderColor; + } + + function SetFillColor($aFillColor) { + $this->fillcolor = $aFillColor; + } + + function SetTabAlign($aAlign) { + $this->align = $aAlign; + } + + function SetWidth($aWidth) { + $this->width = $aWidth ; + } + + function Set($t) { + $this->t = $t; + $this->hide = false; + } + + function SetCorner($aD) { + $this->corner = $aD ; + } + + function Stroke($aImg,$aDummy1=null,$aDummy2=null) { + if( $this->hide ) + return; + $this->boxed = false; + $w = $this->GetWidth($aImg) + 2*$this->posx; + $h = $this->GetTextHeight($aImg) + 2*$this->posy; + + $x = $aImg->left_margin; + $y = $aImg->top_margin; + + if( $this->width === TABTITLE_WIDTHFIT ) { + if( $this->align == 'left' ) { + $p = array($x, $y, + $x, $y-$h+$this->corner, + $x + $this->corner,$y-$h, + $x + $w - $this->corner, $y-$h, + $x + $w, $y-$h+$this->corner, + $x + $w, $y); + } + elseif( $this->align == 'center' ) { + $x += round($aImg->plotwidth/2) - round($w/2); + $p = array($x, $y, + $x, $y-$h+$this->corner, + $x + $this->corner, $y-$h, + $x + $w - $this->corner, $y-$h, + $x + $w, $y-$h+$this->corner, + $x + $w, $y); + } + else { + $x += $aImg->plotwidth -$w; + $p = array($x, $y, + $x, $y-$h+$this->corner, + $x + $this->corner,$y-$h, + $x + $w - $this->corner, $y-$h, + $x + $w, $y-$h+$this->corner, + $x + $w, $y); + } + } + else { + if( $this->width === TABTITLE_WIDTHFULL ) + $w = $aImg->plotwidth ; + else + $w = $this->width ; + + // Make the tab fit the width of the plot area + $p = array($x, $y, + $x, $y-$h+$this->corner, + $x + $this->corner,$y-$h, + $x + $w - $this->corner, $y-$h, + $x + $w, $y-$h+$this->corner, + $x + $w, $y); + + } + if( $this->halign == 'left' ) { + $aImg->SetTextAlign('left','bottom'); + $x += $this->posx; + $y -= $this->posy; + } + elseif( $this->halign == 'center' ) { + $aImg->SetTextAlign('center','bottom'); + $x += $w/2; + $y -= $this->posy; + } + else { + $aImg->SetTextAlign('right','bottom'); + $x += $w - $this->posx; + $y -= $this->posy; + } + + $aImg->SetColor($this->fillcolor); + $aImg->FilledPolygon($p); + + $aImg->SetColor($this->bordercolor); + $aImg->Polygon($p,true); + + $aImg->SetColor($this->color); + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $aImg->StrokeText($x,$y,$this->t,0,'center'); + } + +} + +//=================================================== +// CLASS SuperScriptText +// Description: Format a superscript text +//=================================================== +class SuperScriptText extends Text { + private $iSuper=""; + private $sfont_family="",$sfont_style="",$sfont_size=8; + private $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65; + private $iSDir=0; + private $iSimple=false; + + function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) { + parent::Text($aTxt,$aXAbsPos,$aYAbsPos); + $this->iSuper = $aSuper; + } + + function FromReal($aVal,$aPrecision=2) { + // Convert a floating point number to scientific notation + $neg=1.0; + if( $aVal < 0 ) { + $neg = -1.0; + $aVal = -$aVal; + } + + $l = floor(log10($aVal)); + $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision)); + $a *= $neg; + if( $this->iSimple && ($a == 1 || $a==-1) ) $a = ''; + + if( $a != '' ) + $this->t = $a.' * 10'; + else { + if( $neg == 1 ) + $this->t = '10'; + else + $this->t = '-10'; + } + $this->iSuper = $l; + } + + function Set($aTxt,$aSuper="") { + $this->t = $aTxt; + $this->iSuper = $aSuper; + } + + function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) { + $this->sfont_family = $aFontFam; + $this->sfont_style = $aFontStyle; + $this->sfont_size = $aFontSize; + } + + // Total width of text + function GetWidth($aImg) { + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $w = $aImg->GetTextWidth($this->t); + $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size); + $w += $aImg->GetTextWidth($this->iSuper); + $w += $this->iSuperMargin; + return $w; + } + + // Hight of font (approximate the height of the text) + function GetFontHeight($aImg) { + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $h = $aImg->GetFontHeight(); + $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size); + $h += $aImg->GetFontHeight(); + return $h; + } + + // Hight of text + function GetTextHeight($aImg) { + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $h = $aImg->GetTextHeight($this->t); + $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size); + $h += $aImg->GetTextHeight($this->iSuper); + return $h; + } + + function Stroke($aImg,$ax=-1,$ay=-1) { + + // To position the super script correctly we need different + // cases to handle the alignmewnt specified since that will + // determine how we can interpret the x,y coordinates + + $w = parent::GetWidth($aImg); + $h = parent::GetTextHeight($aImg); + switch( $this->valign ) { + case 'top': + $sy = $this->y; + break; + case 'center': + $sy = $this->y - $h/2; + break; + case 'bottom': + $sy = $this->y - $h; + break; + default: + JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text'); + break; + } + + switch( $this->halign ) { + case 'left': + $sx = $this->x + $w; + break; + case 'center': + $sx = $this->x + $w/2; + break; + case 'right': + $sx = $this->x; + break; + default: + JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text'); + break; + } + + $sx += $this->iSuperMargin; + $sy += $this->iVertOverlap; + + // Should we automatically determine the font or + // has the user specified it explicetly? + if( $this->sfont_family == "" ) { + if( $this->font_family <= FF_FONT2 ) { + if( $this->font_family == FF_FONT0 ) { + $sff = FF_FONT0; + } + elseif( $this->font_family == FF_FONT1 ) { + if( $this->font_style == FS_NORMAL ) + $sff = FF_FONT0; + else + $sff = FF_FONT1; + } + else { + $sff = FF_FONT1; + } + $sfs = $this->font_style; + $sfz = $this->font_size; + } + else { + // TTF fonts + $sff = $this->font_family; + $sfs = $this->font_style; + $sfz = floor($this->font_size*$this->iSuperScale); + if( $sfz < 8 ) $sfz = 8; + } + $this->sfont_family = $sff; + $this->sfont_style = $sfs; + $this->sfont_size = $sfz; + } + else { + $sff = $this->sfont_family; + $sfs = $this->sfont_style; + $sfz = $this->sfont_size; + } + + parent::Stroke($aImg,$ax,$ay); + + + // For the builtin fonts we need to reduce the margins + // since the bounding bx reported for the builtin fonts + // are much larger than for the TTF fonts. + if( $sff <= FF_FONT2 ) { + $sx -= 2; + $sy += 3; + } + + $aImg->SetTextAlign('left','bottom'); + $aImg->SetFont($sff,$sfs,$sfz); + $aImg->PushColor($this->color); + $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left'); + $aImg->PopColor(); + } +} + + +//=================================================== +// CLASS Grid +// Description: responsible for drawing grid lines in graph +//=================================================== +class Grid { + protected $img; + protected $scale; + protected $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD'; + protected $type="solid"; + protected $show=false, $showMinor=false,$weight=1; + protected $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF'); +//--------------- +// CONSTRUCTOR + function Grid($aAxis) { + $this->scale = $aAxis->scale; + $this->img = $aAxis->img; + } +//--------------- +// PUBLIC METHODS + function SetColor($aMajColor,$aMinColor=false) { + $this->grid_color=$aMajColor; + if( $aMinColor === false ) + $aMinColor = $aMajColor ; + $this->grid_mincolor = $aMinColor; + } + + function SetWeight($aWeight) { + $this->weight=$aWeight; + } + + // Specify if grid should be dashed, dotted or solid + function SetLineStyle($aType) { + $this->type = $aType; + } + + // Decide if both major and minor grid should be displayed + function Show($aShowMajor=true,$aShowMinor=false) { + $this->show=$aShowMajor; + $this->showMinor=$aShowMinor; + } + + function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') { + $this->fill = $aFlg; + $this->fillcolor = array( $aColor1, $aColor2 ); + } + + // Display the grid + function Stroke() { + if( $this->showMinor && !$this->scale->textscale ) { + $tmp = $this->grid_color; + $this->grid_color = $this->grid_mincolor; + $this->DoStroke($this->scale->ticks->ticks_pos); + + $this->grid_color = $tmp; + $this->DoStroke($this->scale->ticks->maj_ticks_pos); + } + else { + $this->DoStroke($this->scale->ticks->maj_ticks_pos); + } + } + +//-------------- +// Private methods + // Draw the grid + function DoStroke($aTicksPos) { + if( !$this->show ) + return; + $nbrgrids = count($aTicksPos); + + if( $this->scale->type=="y" ) { + $xl=$this->img->left_margin; + $xr=$this->img->width-$this->img->right_margin; + + if( $this->fill ) { + // Draw filled areas + $y2 = $aTicksPos[0]; + $i=1; + while( $i < $nbrgrids ) { + $y1 = $y2; + $y2 = $aTicksPos[$i++]; + $this->img->SetColor($this->fillcolor[$i & 1]); + $this->img->FilledRectangle($xl,$y1,$xr,$y2); + } + } + + $this->img->SetColor($this->grid_color); + $this->img->SetLineWeight($this->weight); + + // Draw grid lines + switch( $this->type ) { + case "solid": $style = LINESTYLE_SOLID; break; + case "dotted": $style = LINESTYLE_DOTTED; break; + case "dashed": $style = LINESTYLE_DASHED; break; + case "longdashed": $style = LINESTYLE_LONGDASH; break; + default: + $style = LINESTYLE_SOLID; break; + } + + for($i=0; $i < $nbrgrids; ++$i) { + $y=$aTicksPos[$i]; + $this->img->StyleLine($xl,$y,$xr,$y,$style); + } + } + elseif( $this->scale->type=="x" ) { + $yu=$this->img->top_margin; + $yl=$this->img->height-$this->img->bottom_margin; + $limit=$this->img->width-$this->img->right_margin; + + if( $this->fill ) { + // Draw filled areas + $x2 = $aTicksPos[0]; + $i=1; + while( $i < $nbrgrids ) { + $x1 = $x2; + $x2 = min($aTicksPos[$i++],$limit) ; + $this->img->SetColor($this->fillcolor[$i & 1]); + $this->img->FilledRectangle($x1,$yu,$x2,$yl); + } + } + + $this->img->SetColor($this->grid_color); + $this->img->SetLineWeight($this->weight); + + // We must also test for limit since we might have + // an offset and the number of ticks is calculated with + // assumption offset==0 so we might end up drawing one + // to many gridlines + $i=0; + $x=$aTicksPos[$i]; + while( $itype == "solid" ) + $this->img->Line($x,$yl,$x,$yu); + elseif( $this->type == "dotted" ) + $this->img->DashedLine($x,$yl,$x,$yu,1,6); + elseif( $this->type == "dashed" ) + $this->img->DashedLine($x,$yl,$x,$yu,2,4); + elseif( $this->type == "longdashed" ) + $this->img->DashedLine($x,$yl,$x,$yu,8,6); + ++$i; + } + } + else { + JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']'); + } + return true; + } +} // Class + +//=================================================== +// CLASS Axis +// Description: Defines X and Y axis. Notes that at the +// moment the code is not really good since the axis on +// several occasion must know wheter it's an X or Y axis. +// This was a design decision to make the code easier to +// follow. +//=================================================== +class AxisPrototype { + public $scale=null; + public $img=null; + public $hide=false,$hide_labels=false; + public $title=null; + public $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0; + public $tick_step=1; + public $pos = false; + public $ticks_label = array(); + + protected $weight=1; + protected $color=array(0,0,0),$label_color=array(0,0,0); + protected $ticks_label_colors=null; + protected $show_first_label=true,$show_last_label=true; + protected $label_step=1; // Used by a text axis to specify what multiple of major steps + // should be labeled. + protected $labelPos=0; // Which side of the axis should the labels be? + protected $title_adjust,$title_margin,$title_side=SIDE_LEFT; + protected $tick_label_margin=7; + protected $label_halign = '',$label_valign = '', $label_para_align='left'; + protected $hide_line=false; + protected $iDeltaAbsPos=0; + +//--------------- +// CONSTRUCTOR + function Axis($img,$aScale,$color=array(0,0,0)) { + $this->img = $img; + $this->scale = $aScale; + $this->color = $color; + $this->title=new Text(""); + + if( $aScale->type=="y" ) { + $this->title_margin = 25; + $this->title_adjust="middle"; + $this->title->SetOrientation(90); + $this->tick_label_margin=7; + $this->labelPos=SIDE_LEFT; + } + else { + $this->title_margin = 5; + $this->title_adjust="high"; + $this->title->SetOrientation(0); + $this->tick_label_margin=7; + $this->labelPos=SIDE_DOWN; + $this->title_side=SIDE_DOWN; + } + } +//--------------- +// PUBLIC METHODS + + function SetLabelFormat($aFormStr) { + $this->scale->ticks->SetLabelFormat($aFormStr); + } + + function SetLabelFormatString($aFormStr,$aDate=false) { + $this->scale->ticks->SetLabelFormat($aFormStr,$aDate); + } + + function SetLabelFormatCallback($aFuncName) { + $this->scale->ticks->SetFormatCallback($aFuncName); + } + + function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') { + $this->label_halign = $aHAlign; + $this->label_valign = $aVAlign; + $this->label_para_align = $aParagraphAlign; + } + + // Don't display the first label + function HideFirstTickLabel($aShow=false) { + $this->show_first_label=$aShow; + } + + function HideLastTickLabel($aShow=false) { + $this->show_last_label=$aShow; + } + + // Manually specify the major and (optional) minor tick position and labels + function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) { + $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels); + } + + // Manually specify major tick positions and optional labels + function SetMajTickPositions($aMajPos,$aLabels=NULL) { + $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels); + } + + // Hide minor or major tick marks + function HideTicks($aHideMinor=true,$aHideMajor=true) { + $this->scale->ticks->SupressMinorTickMarks($aHideMinor); + $this->scale->ticks->SupressTickMarks($aHideMajor); + } + + // Hide zero label + function HideZeroLabel($aFlag=true) { + $this->scale->ticks->SupressZeroLabel(); + } + + function HideFirstLastLabel() { + // The two first calls to ticks method will supress + // automatically generated scale values. However, that + // will not affect manually specified value, e.g text-scales. + // therefor we also make a kludge here to supress manually + // specified scale labels. + $this->scale->ticks->SupressLast(); + $this->scale->ticks->SupressFirst(); + $this->show_first_label = false; + $this->show_last_label = false; + } + + // Hide the axis + function Hide($aHide=true) { + $this->hide=$aHide; + } + + // Hide the actual axis-line, but still print the labels + function HideLine($aHide=true) { + $this->hide_line = $aHide; + } + + function HideLabels($aHide=true) { + $this->hide_labels = $aHide; + } + + + // Weight of axis + function SetWeight($aWeight) { + $this->weight = $aWeight; + } + + // Axis color + function SetColor($aColor,$aLabelColor=false) { + $this->color = $aColor; + if( !$aLabelColor ) $this->label_color = $aColor; + else $this->label_color = $aLabelColor; + } + + // Title on axis + function SetTitle($aTitle,$aAdjustAlign="high") { + $this->title->Set($aTitle); + $this->title_adjust=$aAdjustAlign; + } + + // Specify distance from the axis + function SetTitleMargin($aMargin) { + $this->title_margin=$aMargin; + } + + // Which side of the axis should the axis title be? + function SetTitleSide($aSideOfAxis) { + $this->title_side = $aSideOfAxis; + } + + // Utility function to set the direction for tick marks + function SetTickDirection($aDir) { + // Will be deprecated from 1.7 + if( ERR_DEPRECATED ) + JpGraphError::RaiseL(25055);//('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead'); + $this->scale->ticks->SetSide($aDir); + } + + function SetTickSide($aDir) { + $this->scale->ticks->SetSide($aDir); + } + + // Specify text labels for the ticks. One label for each data point + function SetTickLabels($aLabelArray,$aLabelColorArray=null) { + $this->ticks_label = $aLabelArray; + $this->ticks_label_colors = $aLabelColorArray; + } + + // How far from the axis should the labels be drawn + function SetTickLabelMargin($aMargin) { + if( ERR_DEPRECATED ) + JpGraphError::RaiseL(25056);//('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.'); + $this->tick_label_margin=$aMargin; + } + + function SetLabelMargin($aMargin) { + $this->tick_label_margin=$aMargin; + } + + // Specify that every $step of the ticks should be displayed starting + // at $start + // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD + function SetTextTicks($step,$start=0) { + JpGraphError::RaiseL(25057);//(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead."); + } + + // Specify that every $step of the ticks should be displayed starting + // at $start + function SetTextTickInterval($aStep,$aStart=0) { + $this->scale->ticks->SetTextLabelStart($aStart); + $this->tick_step=$aStep; + } + + // Specify that every $step tick mark should have a label + // should be displayed starting + function SetTextLabelInterval($aStep) { + if( $aStep < 1 ) + JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1."); + $this->label_step=$aStep; + } + + // Which side of the axis should the labels be on? + function SetLabelPos($aSidePos) { + // This will be deprecated from 1.7 + if( ERR_DEPRECATED ) + JpGraphError::RaiseL(25059);//('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.'); + $this->labelPos=$aSidePos; + } + + function SetLabelSide($aSidePos) { + $this->labelPos=$aSidePos; + } + + // Set the font + function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) { + $this->font_family = $aFamily; + $this->font_style = $aStyle; + $this->font_size = $aSize; + } + + // Position for axis line on the "other" scale + function SetPos($aPosOnOtherScale) { + $this->pos=$aPosOnOtherScale; + } + + // Set the position of the axis to be X-pixels delta to the right + // of the max X-position (used to position the multiple Y-axis) + function SetPosAbsDelta($aDelta) { + $this->iDeltaAbsPos=$aDelta; + } + + // Specify the angle for the tick labels + function SetLabelAngle($aAngle) { + $this->label_angle = $aAngle; + } + +} // Class + + +//=================================================== +// CLASS Axis +// Description: Defines X and Y axis. Notes that at the +// moment the code is not really good since the axis on +// several occasion must know wheter it's an X or Y axis. +// This was a design decision to make the code easier to +// follow. +//=================================================== +class Axis extends AxisPrototype { + + function Axis($img,$aScale,$color=array(0,0,0)) { + parent::Axis($img,$aScale,$color); + } + + // Stroke the axis. + function Stroke($aOtherAxisScale,$aStrokeLabels=true) { + if( $this->hide ) return; + if( is_numeric($this->pos) ) { + $pos=$aOtherAxisScale->Translate($this->pos); + } + else { // Default to minimum of other scale if pos not set + if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) { + $pos = $aOtherAxisScale->scale_abs[0]; + } + elseif($this->pos == "max") { + $pos = $aOtherAxisScale->scale_abs[1]; + } + else { // If negative set x-axis at 0 + $this->pos=0; + $pos=$aOtherAxisScale->Translate(0); + } + } + $pos += $this->iDeltaAbsPos; + $this->img->SetLineWeight($this->weight); + $this->img->SetColor($this->color); + $this->img->SetFont($this->font_family,$this->font_style,$this->font_size); + if( $this->scale->type == "x" ) { + if( !$this->hide_line ) + $this->img->FilledRectangle($this->img->left_margin,$pos, + $this->img->width-$this->img->right_margin,$pos+$this->weight-1); + if( $this->title_side == SIDE_DOWN ) { + $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin; + $yalign = 'top'; + } + else { + $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin; + $yalign = 'bottom'; + } + + if( $this->title_adjust=='high' ) + $this->title->SetPos($this->img->width-$this->img->right_margin,$y,'right',$yalign); + elseif( $this->title_adjust=='middle' || $this->title_adjust=='center' ) + $this->title->SetPos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,'center',$yalign); + elseif($this->title_adjust=='low') + $this->title->SetPos($this->img->left_margin,$y,'left',$yalign); + else { + JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')'); + } + } + elseif( $this->scale->type == "y" ) { + // Add line weight to the height of the axis since + // the x-axis could have a width>1 and we want the axis to fit nicely together. + if( !$this->hide_line ) + $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin, + $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1); + $x=$pos ; + if( $this->title_side == SIDE_LEFT ) { + $x -= $this->title_margin; + $x -= $this->title->margin; + $halign="right"; + } + else { + $x += $this->title_margin; + $x += $this->title->margin; + $halign="left"; + } + // If the user has manually specified an hor. align + // then we override the automatic settings with this + // specifed setting. Since default is 'left' we compare + // with that. (This means a manually set 'left' align + // will have no effect.) + if( $this->title->halign != 'left' ) + $halign = $this->title->halign; + if( $this->title_adjust=="high" ) + $this->title->SetPos($x,$this->img->top_margin,$halign,"top"); + elseif($this->title_adjust=="middle" || $this->title_adjust=="center") + $this->title->SetPos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center"); + elseif($this->title_adjust=="low") + $this->title->SetPos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom"); + else + JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')'); + + } + $this->scale->ticks->Stroke($this->img,$this->scale,$pos); + if( $aStrokeLabels ) { + if( !$this->hide_labels ) + $this->StrokeLabels($pos); + $this->title->Stroke($this->img); + } + } + +//--------------- +// PRIVATE METHODS + // Draw all the tick labels on major tick marks + function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) { + + $this->img->SetColor($this->label_color); + $this->img->SetFont($this->font_family,$this->font_style,$this->font_size); + $yoff=$this->img->GetFontHeight()/2; + + // Only draw labels at major tick marks + $nbr = count($this->scale->ticks->maj_ticks_label); + + // We have the option to not-display the very first mark + // (Usefull when the first label might interfere with another + // axis.) + $i = $this->show_first_label ? 0 : 1 ; + if( !$this->show_last_label ) --$nbr; + // Now run through all labels making sure we don't overshoot the end + // of the scale. + $ncolor=0; + if( isset($this->ticks_label_colors) ) + $ncolor=count($this->ticks_label_colors); + while( $i<$nbr ) { + // $tpos holds the absolute text position for the label + $tpos=$this->scale->ticks->maj_ticklabels_pos[$i]; + + // Note. the $limit is only used for the x axis since we + // might otherwise overshoot if the scale has been centered + // This is due to us "loosing" the last tick mark if we center. + if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) { + return; + } + // we only draw every $label_step label + if( ($i % $this->label_step)==0 ) { + + // Set specific label color if specified + if( $ncolor > 0 ) + $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]); + + // If the label has been specified use that and in other case + // just label the mark with the actual scale value + $m=$this->scale->ticks->GetMajor(); + + // ticks_label has an entry for each data point and is the array + // that holds the labels set by the user. If the user hasn't + // specified any values we use whats in the automatically asigned + // labels in the maj_ticks_label + if( isset($this->ticks_label[$i*$m]) ) + $label=$this->ticks_label[$i*$m]; + else { + if( $aAbsLabel ) + $label=abs($this->scale->ticks->maj_ticks_label[$i]); + else + $label=$this->scale->ticks->maj_ticks_label[$i]; + if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) { + ++$label; + } + } + + if( $this->scale->type == "x" ) { + if( $this->labelPos == SIDE_DOWN ) { + if( $this->label_angle==0 || $this->label_angle==90 ) { + if( $this->label_halign=='' && $this->label_valign=='') + $this->img->SetTextAlign('center','top'); + else + $this->img->SetTextAlign($this->label_halign,$this->label_valign); + + } + else { + if( $this->label_halign=='' && $this->label_valign=='') + $this->img->SetTextAlign("right","top"); + else + $this->img->SetTextAlign($this->label_halign,$this->label_valign); + } + $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin+1,$label, + $this->label_angle,$this->label_para_align); + } + else { + if( $this->label_angle==0 || $this->label_angle==90 ) { + if( $this->label_halign=='' && $this->label_valign=='') + $this->img->SetTextAlign("center","bottom"); + else + $this->img->SetTextAlign($this->label_halign,$this->label_valign); + } + else { + if( $this->label_halign=='' && $this->label_valign=='') + $this->img->SetTextAlign("right","bottom"); + else + $this->img->SetTextAlign($this->label_halign,$this->label_valign); + } + $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin-1,$label, + $this->label_angle,$this->label_para_align); + } + } + else { + // scale->type == "y" + //if( $this->label_angle!=0 ) + //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis"); + if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis + if( $this->label_halign=='' && $this->label_valign=='') + $this->img->SetTextAlign("right","center"); + else + $this->img->SetTextAlign($this->label_halign,$this->label_valign); + $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align); + } + else { // To the right of the y-axis + if( $this->label_halign=='' && $this->label_valign=='') + $this->img->SetTextAlign("left","center"); + else + $this->img->SetTextAlign($this->label_halign,$this->label_valign); + $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align); + } + } + } + ++$i; + } + } + +} + + +//=================================================== +// CLASS Ticks +// Description: Abstract base class for drawing linear and logarithmic +// tick marks on axis +//=================================================== +class Ticks { + public $label_formatstr=''; // C-style format string to use for labels + public $label_formfunc=''; + public $label_dateformatstr=''; + public $direction=1; // Should ticks be in(=1) the plot area or outside (=-1) + public $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false; + public $maj_ticks_pos = array(), $maj_ticklabels_pos = array(), + $ticks_pos = array(), $maj_ticks_label = array(); + public $precision; + + protected $minor_abs_size=3, $major_abs_size=5; + protected $scale; + protected $is_set=false; + protected $supress_zerolabel=false,$supress_first=false; + protected $mincolor="",$majcolor=""; + protected $weight=1; + protected $label_usedateformat=FALSE; + +//--------------- +// CONSTRUCTOR + function Ticks($aScale) { + $this->scale=$aScale; + $this->precision = -1; + } + +//--------------- +// PUBLIC METHODS + // Set format string for automatic labels + function SetLabelFormat($aFormatString,$aDate=FALSE) { + $this->label_formatstr=$aFormatString; + $this->label_usedateformat=$aDate; + } + + function SetLabelDateFormat($aFormatString) { + $this->label_dateformatstr=$aFormatString; + } + + function SetFormatCallback($aCallbackFuncName) { + $this->label_formfunc = $aCallbackFuncName; + } + + // Don't display the first zero label + function SupressZeroLabel($aFlag=true) { + $this->supress_zerolabel=$aFlag; + } + + // Don't display minor tick marks + function SupressMinorTickMarks($aHide=true) { + $this->supress_minor_tickmarks=$aHide; + } + + // Don't display major tick marks + function SupressTickMarks($aHide=true) { + $this->supress_tickmarks=$aHide; + } + + // Hide the first tick mark + function SupressFirst($aHide=true) { + $this->supress_first=$aHide; + } + + // Hide the last tick mark + function SupressLast($aHide=true) { + $this->supress_last=$aHide; + } + + // Size (in pixels) of minor tick marks + function GetMinTickAbsSize() { + return $this->minor_abs_size; + } + + // Size (in pixels) of major tick marks + function GetMajTickAbsSize() { + return $this->major_abs_size; + } + + function SetSize($aMajSize,$aMinSize=3) { + $this->major_abs_size = $aMajSize; + $this->minor_abs_size = $aMinSize; + } + + // Have the ticks been specified + function IsSpecified() { + return $this->is_set; + } + + // Specify number of decimals in automatic labels + // Deprecated from 1.4. Use SetFormatString() instead + function SetPrecision($aPrecision) { + if( ERR_DEPRECATED ) + JpGraphError::RaiseL(25063);//('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead'); + $this->precision=$aPrecision; + } + + function SetSide($aSide) { + $this->direction=$aSide; + } + + // Which side of the axis should the ticks be on + function SetDirection($aSide=SIDE_RIGHT) { + $this->direction=$aSide; + } + + // Set colors for major and minor tick marks + function SetMarkColor($aMajorColor,$aMinorColor="") { + $this->SetColor($aMajorColor,$aMinorColor); + } + + function SetColor($aMajorColor,$aMinorColor="") { + $this->majcolor=$aMajorColor; + + // If not specified use same as major + if( $aMinorColor=="" ) + $this->mincolor=$aMajorColor; + else + $this->mincolor=$aMinorColor; + } + + function SetWeight($aWeight) { + $this->weight=$aWeight; + } + +} // Class + +//=================================================== +// CLASS LinearTicks +// Description: Draw linear ticks on axis +//=================================================== +class LinearTicks extends Ticks { + public $minor_step=1, $major_step=2; + public $xlabel_offset=0,$xtick_offset=0; + private $label_offset=0; // What offset should the displayed label have + // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc + private $text_label_start=0; + private $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL; + private $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time + +//--------------- +// CONSTRUCTOR + function LinearTicks() { + $this->precision = -1; + } + +//--------------- +// PUBLIC METHODS + + + // Return major step size in world coordinates + function GetMajor() { + return $this->major_step; + } + + // Return minor step size in world coordinates + function GetMinor() { + return $this->minor_step; + } + + // Set Minor and Major ticks (in world coordinates) + function Set($aMajStep,$aMinStep=false) { + if( $aMinStep==false ) + $aMinStep=$aMajStep; + + if( $aMajStep <= 0 || $aMinStep <= 0 ) { + JpGraphError::RaiseL(25064); +//(" Minor or major step size is 0. Check that you haven't got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem."); + } + + $this->major_step=$aMajStep; + $this->minor_step=$aMinStep; + $this->is_set = true; + } + + function SetMajTickPositions($aMajPos,$aLabels=NULL) { + $this->SetTickPositions($aMajPos,NULL,$aLabels); + } + + function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) { + if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) { + JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()'); + return; + } + $n=count($aMajPos); + if( is_array($aLabels) && (count($aLabels) != $n) ) { + JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.'); + return; + } + $this->iManualTickPos = $aMajPos; + $this->iManualMinTickPos = $aMinPos; + $this->iManualTickLabels = $aLabels; + } + + // Specify all the tick positions manually and possible also the exact labels + function _doManualTickPos($aScale) { + $n=count($this->iManualTickPos); + $m=count($this->iManualMinTickPos); + $doLbl=count($this->iManualTickLabels) > 0; + + $this->maj_ticks_pos = array(); + $this->maj_ticklabels_pos = array(); + $this->ticks_pos = array(); + + // Now loop through the supplied positions and translate them to screen coordinates + // and store them in the maj_label_positions + $minScale = $aScale->scale[0]; + $maxScale = $aScale->scale[1]; + $j=0; + for($i=0; $i < $n ; ++$i ) { + // First make sure that the first tick is not lower than the lower scale value + if( !isset($this->iManualTickPos[$i]) || + $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) { + continue; + } + + + $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]); + $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j]; + + // Set the minor tick marks the same as major if not specified + if( $m <= 0 ) { + $this->ticks_pos[$j] = $this->maj_ticks_pos[$j]; + } + + if( $doLbl ) { + $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i]; + } + else { + $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n); + } + ++$j; + } + + // Some sanity check + if( count($this->maj_ticks_pos) < 2 ) { + JpGraphError::RaiseL(25067);//('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tickl marks.'); + } + + // Setup the minor tick marks + $j=0; + for($i=0; $i < $m; ++$i ) { + if( empty($this->iManualMinTickPos[$i]) || + $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) + continue; + $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]); + ++$j; + } + } + + function _doAutoTickPos($aScale) { + $maj_step_abs = $aScale->scale_factor*$this->major_step; + $min_step_abs = $aScale->scale_factor*$this->minor_step; + + if( $min_step_abs==0 || $maj_step_abs==0 ) { + JpGraphError::RaiseL(25068);//("A plot has an illegal scale. This could for example be that you are trying to use text autoscaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only '-' or 'x')"); + } + // We need to make this an int since comparing it below + // with the result from round() can give wrong result, such that + // (40 < 40) == TRUE !!! + $limit = (int)$aScale->scale_abs[1]; + + if( $aScale->textscale ) { + // This can only be true for a X-scale (horizontal) + // Define ticks for a text scale. This is slightly different from a + // normal linear type of scale since the position might be adjusted + // and the labels start at on + $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset; + $start_abs=$aScale->scale_factor*$this->text_label_start; + $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1; + + $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs; + for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) { + // Apply format to label + $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks); + $label+=$this->major_step; + + // The x-position of the tick marks can be different from the labels. + // Note that we record the tick position (not the label) so that the grid + // happen upon tick marks and not labels. + $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs; + $this->maj_ticks_pos[$i]=$xtick; + $this->maj_ticklabels_pos[$i] = round($x); + $x += $maj_step_abs; + } + } + else { + $label = $aScale->GetMinVal(); + $abs_pos = $aScale->scale_abs[0]; + $j=0; $i=0; + $step = round($maj_step_abs/$min_step_abs); + if( $aScale->type == "x" ) { + // For a normal linear type of scale the major ticks will always be multiples + // of the minor ticks. In order to avoid any rounding issues the major ticks are + // defined as every "step" minor ticks and not calculated separately + $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1; + while( round($abs_pos) <= $limit ) { + $this->ticks_pos[] = round($abs_pos); + $this->ticks_label[] = $label; + if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks ) { + $this->maj_ticks_pos[$j] = round($abs_pos); + $this->maj_ticklabels_pos[$j] = round($abs_pos); + $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks); + ++$j; + } + ++$i; + $abs_pos += $min_step_abs; + $label+=$this->minor_step; + } + } + elseif( $aScale->type == "y" ) { + $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1; + while( round($abs_pos) >= $limit ) { + $this->ticks_pos[$i] = round($abs_pos); + $this->ticks_label[$i]=$label; + if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks) { + $this->maj_ticks_pos[$j] = round($abs_pos); + $this->maj_ticklabels_pos[$j] = round($abs_pos); + $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks); + ++$j; + } + ++$i; + $abs_pos += $min_step_abs; + $label += $this->minor_step; + } + } + } + } + + function AdjustForDST($aFlg=true) { + $this->iAdjustForDST = $aFlg; + } + + + function _doLabelFormat($aVal,$aIdx,$aNbrTicks) { + + // If precision hasn't been specified set it to a sensible value + if( $this->precision==-1 ) { + $t = log10($this->minor_step); + if( $t > 0 ) + $precision = 0; + else + $precision = -floor($t); + } + else + $precision = $this->precision; + + if( $this->label_formfunc != '' ) { + $f=$this->label_formfunc; + $l = call_user_func($f,$aVal); + } + elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) { + if( $this->label_usedateformat ) { + // Adjust the value to take daylight savings into account + if (date("I",$aVal)==1 && $this->iAdjustForDST ) // DST + $aVal+=3600; + + $l = date($this->label_formatstr,$aVal); + if( $this->label_formatstr == 'W' ) { + // If we use week formatting then add a single 'w' in front of the + // week number to differentiate it from dates + $l = 'w'.$l; + } + } + else { + if( $this->label_dateformatstr !== '' ) { + // Adjust the value to take daylight savings into account + if (date("I",$aVal)==1 && $this->iAdjustForDST ) // DST + $aVal+=3600; + + $l = date($this->label_dateformatstr,$aVal); + if( $this->label_formatstr == 'W' ) { + // If we use week formatting then add a single 'w' in front of the + // week number to differentiate it from dates + $l = 'w'.$l; + } + } + else + $l = sprintf($this->label_formatstr,$aVal); + } + } + else { + $l = sprintf('%01.'.$precision.'f',round($aVal,$precision)); + } + + if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) || + ($this->supress_last && $aIdx==$aNbrTicks-1) ) { + $l=''; + } + return $l; + } + + // Stroke ticks on either X or Y axis + function _StrokeTicks($aImg,$aScale,$aPos) { + $hor = $aScale->type == 'x'; + $aImg->SetLineWeight($this->weight); + + // We need to make this an int since comparing it below + // with the result from round() can give wrong result, such that + // (40 < 40) == TRUE !!! + $limit = (int)$aScale->scale_abs[1]; + + // A text scale doesn't have any minor ticks + if( !$aScale->textscale ) { + // Stroke minor ticks + $yu = $aPos - $this->direction*$this->GetMinTickAbsSize(); + $xr = $aPos + $this->direction*$this->GetMinTickAbsSize(); + $n = count($this->ticks_pos); + for($i=0; $i < $n; ++$i ) { + if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) { + if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor); + if( $hor ) { + //if( $this->ticks_pos[$i] <= $limit ) + $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu); + } + else { + //if( $this->ticks_pos[$i] >= $limit ) + $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]); + } + if( $this->mincolor!="" ) $aImg->PopColor(); + } + } + } + + // Stroke major ticks + $yu = $aPos - $this->direction*$this->GetMajTickAbsSize(); + $xr = $aPos + $this->direction*$this->GetMajTickAbsSize(); + $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1; + $n = count($this->maj_ticks_pos); + for($i=0; $i < $n ; ++$i ) { + if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) { + if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor); + if( $hor ) { + //if( $this->maj_ticks_pos[$i] <= $limit ) + $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu); + } + else { + //if( $this->maj_ticks_pos[$i] >= $limit ) + $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]); + } + if( $this->majcolor!="" ) $aImg->PopColor(); + } + } + + } + + // Draw linear ticks + function Stroke($aImg,$aScale,$aPos) { + if( $this->iManualTickPos != NULL ) + $this->_doManualTickPos($aScale); + else + $this->_doAutoTickPos($aScale); + $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' ); + } + +//--------------- +// PRIVATE METHODS + // Spoecify the offset of the displayed tick mark with the tick "space" + // Legal values for $o is [0,1] used to adjust where the tick marks and label + // should be positioned within the major tick-size + // $lo specifies the label offset and $to specifies the tick offset + // this comes in handy for example in bar graphs where we wont no offset for the + // tick but have the labels displayed halfway under the bars. + function SetXLabelOffset($aLabelOff,$aTickOff=-1) { + $this->xlabel_offset=$aLabelOff; + if( $aTickOff==-1 ) // Same as label offset + $this->xtick_offset=$aLabelOff; + else + $this->xtick_offset=$aTickOff; + if( $aLabelOff>0 ) + $this->SupressLast(); // The last tick wont fit + } + + // Which tick label should we start with? + function SetTextLabelStart($aTextLabelOff) { + $this->text_label_start=$aTextLabelOff; + } + +} // Class + +//=================================================== +// CLASS LinearScale +// Description: Handle linear scaling between screen and world +//=================================================== +class LinearScale { + public $textscale=false; // Just a flag to let the Plot class find out if + // we are a textscale or not. This is a cludge since + // this information is available in Graph::axtype but + // we don't have access to the graph object in the Plots + // stroke method. So we let graph store the status here + // when the linear scale is created. A real cludge... + public $type; // is this x or y scale ? + public $ticks=null; // Store ticks + public $text_scale_off = 0; + public $scale_abs=array(0,0); + public $scale_factor; // Scale factor between world and screen + public $off; // Offset between image edge and plot area + public $scale=array(0,0); + public $name = 'lin'; + public $auto_ticks=false; // When using manual scale should the ticks be automatically set? + public $world_abs_size; // Plot area size in pixels (Needed public in jpgraph_radar.php) + public $world_size; // Plot area size in world coordinates + public $intscale=false; // Restrict autoscale to integers + protected $autoscale_min=false; // Forced minimum value, auto determine max + protected $autoscale_max=false; // Forced maximum value, auto determine min + private $gracetop=0,$gracebottom=0; +//--------------- +// CONSTRUCTOR + function LinearScale($aMin=0,$aMax=0,$aType="y") { + assert($aType=="x" || $aType=="y" ); + assert($aMin<=$aMax); + + $this->type=$aType; + $this->scale=array($aMin,$aMax); + $this->world_size=$aMax-$aMin; + $this->ticks = new LinearTicks(); + } + +//--------------- +// PUBLIC METHODS + // Check if scale is set or if we should autoscale + // We should do this is either scale or ticks has not been set + function IsSpecified() { + if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set + return false; + } + return true; + } + + // Set the minimum data value when the autoscaling is used. + // Usefull if you want a fix minimum (like 0) but have an + // automatic maximum + function SetAutoMin($aMin) { + $this->autoscale_min=$aMin; + } + + // Set the minimum data value when the autoscaling is used. + // Usefull if you want a fix minimum (like 0) but have an + // automatic maximum + function SetAutoMax($aMax) { + $this->autoscale_max=$aMax; + } + + // If the user manually specifies a scale should the ticks + // still be set automatically? + function SetAutoTicks($aFlag=true) { + $this->auto_ticks = $aFlag; + } + + // Specify scale "grace" value (top and bottom) + function SetGrace($aGraceTop,$aGraceBottom=0) { + if( $aGraceTop<0 || $aGraceBottom < 0 ) + JpGraphError::RaiseL(25069);//(" Grace must be larger then 0"); + $this->gracetop=$aGraceTop; + $this->gracebottom=$aGraceBottom; + } + + // Get the minimum value in the scale + function GetMinVal() { + return $this->scale[0]; + } + + // get maximum value for scale + function GetMaxVal() { + return $this->scale[1]; + } + + // Specify a new min/max value for sclae + function Update($aImg,$aMin,$aMax) { + $this->scale=array($aMin,$aMax); + $this->world_size=$aMax-$aMin; + $this->InitConstants($aImg); + } + + // Translate between world and screen + function Translate($aCoord) { + if( !is_numeric($aCoord) ) { + if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) + JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.'); + return 0; + } + else { + return $this->off+($aCoord - $this->scale[0]) * $this->scale_factor; + } + } + + // Relative translate (don't include offset) usefull when we just want + // to know the relative position (in pixels) on the axis + function RelTranslate($aCoord) { + if( !is_numeric($aCoord) ) { + if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) + JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.'); + return 0; + } + else { + return ($aCoord - $this->scale[0]) * $this->scale_factor; + } + } + + // Restrict autoscaling to only use integers + function SetIntScale($aIntScale=true) { + $this->intscale=$aIntScale; + } + + // Calculate an integer autoscale + function IntAutoScale($img,$min,$max,$maxsteps,$majend=true) { + // Make sure limits are integers + $min=floor($min); + $max=ceil($max); + if( abs($min-$max)==0 ) { + --$min; ++$max; + } + $maxsteps = floor($maxsteps); + + $gracetop=round(($this->gracetop/100.0)*abs($max-$min)); + $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min)); + if( is_numeric($this->autoscale_min) ) { + $min = ceil($this->autoscale_min); + if( $min >= $max ) { + JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.'); + } + } + + if( is_numeric($this->autoscale_max) ) { + $max = ceil($this->autoscale_max); + if( $min >= $max ) { + JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.'); + } + } + + if( abs($min-$max ) == 0 ) { + ++$max; + --$min; + } + + $min -= $gracebottom; + $max += $gracetop; + + // First get tickmarks as multiples of 1, 10, ... + if( $majend ) { + list($num1steps,$adj1min,$adj1max,$maj1step) = + $this->IntCalcTicks($maxsteps,$min,$max,1); + } + else { + $adj1min = $min; + $adj1max = $max; + list($num1steps,$maj1step) = + $this->IntCalcTicksFreeze($maxsteps,$min,$max,1); + } + + if( abs($min-$max) > 2 ) { + // Then get tick marks as 2:s 2, 20, ... + if( $majend ) { + list($num2steps,$adj2min,$adj2max,$maj2step) = + $this->IntCalcTicks($maxsteps,$min,$max,5); + } + else { + $adj2min = $min; + $adj2max = $max; + list($num2steps,$maj2step) = + $this->IntCalcTicksFreeze($maxsteps,$min,$max,5); + } + } + else { + $num2steps = 10000; // Dummy high value so we don't choose this + } + + if( abs($min-$max) > 5 ) { + // Then get tickmarks as 5:s 5, 50, 500, ... + if( $majend ) { + list($num5steps,$adj5min,$adj5max,$maj5step) = + $this->IntCalcTicks($maxsteps,$min,$max,2); + } + else { + $adj5min = $min; + $adj5max = $max; + list($num5steps,$maj5step) = + $this->IntCalcTicksFreeze($maxsteps,$min,$max,2); + } + } + else { + $num5steps = 10000; // Dummy high value so we don't choose this + } + + // Check to see whichof 1:s, 2:s or 5:s fit better with + // the requested number of major ticks + $match1=abs($num1steps-$maxsteps); + $match2=abs($num2steps-$maxsteps); + if( !empty($maj5step) && $maj5step > 1 ) + $match5=abs($num5steps-$maxsteps); + else + $match5=10000; // Dummy high value + + // Compare these three values and see which is the closest match + // We use a 0.6 weight to gravitate towards multiple of 5:s + if( $match1 < $match2 ) { + if( $match1 < $match5 ) + $r=1; + else + $r=3; + } + else { + if( $match2 < $match5 ) + $r=2; + else + $r=3; + } + // Minsteps are always the same as maxsteps for integer scale + switch( $r ) { + case 1: + $this->ticks->Set($maj1step,$maj1step); + $this->Update($img,$adj1min,$adj1max); + break; + case 2: + $this->ticks->Set($maj2step,$maj2step); + $this->Update($img,$adj2min,$adj2max); + break; + case 3: + $this->ticks->Set($maj5step,$maj5step); + $this->Update($img,$adj5min,$adj5max); + break; + default: + JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)'); + } + } + + + // Calculate autoscale. Used if user hasn't given a scale and ticks + // $maxsteps is the maximum number of major tickmarks allowed. + function AutoScale($img,$min,$max,$maxsteps,$majend=true) { + if( $this->intscale ) { + $this->IntAutoScale($img,$min,$max,$maxsteps,$majend); + return; + } + if( abs($min-$max) < 0.00001 ) { + // We need some difference to be able to autoscale + // make it 5% above and 5% below value + if( $min==0 && $max==0 ) { // Special case + $min=-1; $max=1; + } + else { + $delta = (abs($max)+abs($min))*0.005; + $min -= $delta; + $max += $delta; + } + } + + $gracetop=($this->gracetop/100.0)*abs($max-$min); + $gracebottom=($this->gracebottom/100.0)*abs($max-$min); + if( is_numeric($this->autoscale_min) ) { + $min = $this->autoscale_min; + if( $min >= $max ) { + JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.'); + } + if( abs($min-$max ) < 0.00001 ) + $max *= 1.2; + } + + if( is_numeric($this->autoscale_max) ) { + $max = $this->autoscale_max; + if( $min >= $max ) { + JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.'); + } + if( abs($min-$max ) < 0.00001 ) + $min *= 0.8; + } + + $min -= $gracebottom; + $max += $gracetop; + + + // First get tickmarks as multiples of 0.1, 1, 10, ... + if( $majend ) { + list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = + $this->CalcTicks($maxsteps,$min,$max,1,2); + } + else { + $adj1min=$min; + $adj1max=$max; + list($num1steps,$min1step,$maj1step) = + $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false); + } + + // Then get tick marks as 2:s 0.2, 2, 20, ... + if( $majend ) { + list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = + $this->CalcTicks($maxsteps,$min,$max,5,2); + } + else { + $adj2min=$min; + $adj2max=$max; + list($num2steps,$min2step,$maj2step) = + $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false); + } + + // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ... + if( $majend ) { + list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = + $this->CalcTicks($maxsteps,$min,$max,2,5); + } + else { + $adj5min=$min; + $adj5max=$max; + list($num5steps,$min5step,$maj5step) = + $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false); + } + + // Check to see whichof 1:s, 2:s or 5:s fit better with + // the requested number of major ticks + $match1=abs($num1steps-$maxsteps); + $match2=abs($num2steps-$maxsteps); + $match5=abs($num5steps-$maxsteps); + // Compare these three values and see which is the closest match + // We use a 0.8 weight to gravitate towards multiple of 5:s + $r=$this->MatchMin3($match1,$match2,$match5,0.8); + switch( $r ) { + case 1: + $this->Update($img,$adj1min,$adj1max); + $this->ticks->Set($maj1step,$min1step); + break; + case 2: + $this->Update($img,$adj2min,$adj2max); + $this->ticks->Set($maj2step,$min2step); + break; + case 3: + $this->Update($img,$adj5min,$adj5max); + $this->ticks->Set($maj5step,$min5step); + break; + } + } + +//--------------- +// PRIVATE METHODS + + // This method recalculates all constants that are depending on the + // margins in the image. If the margins in the image are changed + // this method should be called for every scale that is registred with + // that image. Should really be installed as an observer of that image. + function InitConstants($img) { + if( $this->type=="x" ) { + $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin; + $this->off=$img->left_margin; + $this->scale_factor = 0; + if( $this->world_size > 0 ) + $this->scale_factor=$this->world_abs_size/($this->world_size*1.0); + } + else { // y scale + $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin; + $this->off=$img->top_margin+$this->world_abs_size; + $this->scale_factor = 0; + if( $this->world_size > 0 ) + $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0); + } + $size = $this->world_size * $this->scale_factor; + $this->scale_abs=array($this->off,$this->off + $size); + } + + // Initialize the conversion constants for this scale + // This tries to pre-calculate as much as possible to speed up the + // actual conversion (with Translate()) later on + // $start =scale start in absolute pixels (for x-scale this is an y-position + // and for an y-scale this is an x-position + // $len =absolute length in pixels of scale + function SetConstants($aStart,$aLen) { + $this->world_abs_size=$aLen; + $this->off=$aStart; + + if( $this->world_size<=0 ) { + // This should never ever happen !! + JpGraphError::RaiseL(25074); +//("You have unfortunately stumbled upon a bug in JpGraph. It seems like the scale range is ".$this->world_size." [for ".$this->type." scale]
    Please report Bug #01 to jpgraph@aditus.nu and include the script that gave this error. This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the autoscaling to fail."); + + } + + // scale_factor = number of pixels per world unit + $this->scale_factor=$this->world_abs_size/($this->world_size*1.0); + + // scale_abs = start and end points of scale in absolute pixels + $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor); + } + + + // Calculate number of ticks steps with a specific division + // $a is the divisor of 10**x to generate the first maj tick intervall + // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,... + // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,... + // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,... + // We return a vector of + // [$numsteps,$adjmin,$adjmax,$minstep,$majstep] + // If $majend==true then the first and last marks on the axis will be major + // labeled tick marks otherwise it will be adjusted to the closest min tick mark + function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) { + $diff=$max-$min; + if( $diff==0 ) + $ld=0; + else + $ld=floor(log10($diff)); + + // Gravitate min towards zero if we are close + if( $min>0 && $min < pow(10,$ld) ) $min=0; + + //$majstep=pow(10,$ld-1)/$a; + $majstep=pow(10,$ld)/$a; + $minstep=$majstep/$b; + + $adjmax=ceil($max/$minstep)*$minstep; + $adjmin=floor($min/$minstep)*$minstep; + $adjdiff = $adjmax-$adjmin; + $numsteps=$adjdiff/$majstep; + + while( $numsteps>$maxsteps ) { + $majstep=pow(10,$ld)/$a; + $numsteps=$adjdiff/$majstep; + ++$ld; + } + + $minstep=$majstep/$b; + $adjmin=floor($min/$minstep)*$minstep; + $adjdiff = $adjmax-$adjmin; + if( $majend ) { + $adjmin = floor($min/$majstep)*$majstep; + $adjdiff = $adjmax-$adjmin; + $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin; + } + else + $adjmax=ceil($max/$minstep)*$minstep; + + return array($numsteps,$adjmin,$adjmax,$minstep,$majstep); + } + + function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) { + // Same as CalcTicks but don't adjust min/max values + $diff=$max-$min; + if( $diff==0 ) + $ld=0; + else + $ld=floor(log10($diff)); + + //$majstep=pow(10,$ld-1)/$a; + $majstep=pow(10,$ld)/$a; + $minstep=$majstep/$b; + $numsteps=floor($diff/$majstep); + + while( $numsteps > $maxsteps ) { + $majstep=pow(10,$ld)/$a; + $numsteps=floor($diff/$majstep); + ++$ld; + } + $minstep=$majstep/$b; + return array($numsteps,$minstep,$majstep); + } + + + function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) { + $diff=$max-$min; + if( $diff==0 ) + JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.'); + else + $ld=floor(log10($diff)); + + // Gravitate min towards zero if we are close + if( $min>0 && $min < pow(10,$ld) ) $min=0; + + if( $ld == 0 ) $ld=1; + + if( $a == 1 ) + $majstep = 1; + else + $majstep=pow(10,$ld)/$a; + $adjmax=ceil($max/$majstep)*$majstep; + + $adjmin=floor($min/$majstep)*$majstep; + $adjdiff = $adjmax-$adjmin; + $numsteps=$adjdiff/$majstep; + while( $numsteps>$maxsteps ) { + $majstep=pow(10,$ld)/$a; + $numsteps=$adjdiff/$majstep; + ++$ld; + } + + $adjmin=floor($min/$majstep)*$majstep; + $adjdiff = $adjmax-$adjmin; + if( $majend ) { + $adjmin = floor($min/$majstep)*$majstep; + $adjdiff = $adjmax-$adjmin; + $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin; + } + else + $adjmax=ceil($max/$majstep)*$majstep; + + return array($numsteps,$adjmin,$adjmax,$majstep); + } + + + function IntCalcTicksFreeze($maxsteps,$min,$max,$a) { + // Same as IntCalcTick but don't change min/max values + $diff=$max-$min; + if( $diff==0 ) + JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.'); + else + $ld=floor(log10($diff)); + + if( $ld == 0 ) $ld=1; + + if( $a == 1 ) + $majstep = 1; + else + $majstep=pow(10,$ld)/$a; + + $numsteps=floor($diff/$majstep); + while( $numsteps > $maxsteps ) { + $majstep=pow(10,$ld)/$a; + $numsteps=floor($diff/$majstep); + ++$ld; + } + + return array($numsteps,$majstep); + } + + + + // Determine the minimum of three values witha weight for last value + function MatchMin3($a,$b,$c,$weight) { + if( $a < $b ) { + if( $a < ($c*$weight) ) + return 1; // $a smallest + else + return 3; // $c smallest + } + elseif( $b < ($c*$weight) ) + return 2; // $b smallest + return 3; // $c smallest + } +} // Class + +//=================================================== +// CLASS RGB +// Description: Color definitions as RGB triples +//=================================================== +class RGB { + public $rgb_table; + public $img; + + function RGB($aImg=null) { + $this->img = $aImg; + + // Conversion array between color names and RGB + $this->rgb_table = array( + "aqua"=> array(0,255,255), + "lime"=> array(0,255,0), + "teal"=> array(0,128,128), + "whitesmoke"=>array(245,245,245), + "gainsboro"=>array(220,220,220), + "oldlace"=>array(253,245,230), + "linen"=>array(250,240,230), + "antiquewhite"=>array(250,235,215), + "papayawhip"=>array(255,239,213), + "blanchedalmond"=>array(255,235,205), + "bisque"=>array(255,228,196), + "peachpuff"=>array(255,218,185), + "navajowhite"=>array(255,222,173), + "moccasin"=>array(255,228,181), + "cornsilk"=>array(255,248,220), + "ivory"=>array(255,255,240), + "lemonchiffon"=>array(255,250,205), + "seashell"=>array(255,245,238), + "mintcream"=>array(245,255,250), + "azure"=>array(240,255,255), + "aliceblue"=>array(240,248,255), + "lavender"=>array(230,230,250), + "lavenderblush"=>array(255,240,245), + "mistyrose"=>array(255,228,225), + "white"=>array(255,255,255), + "black"=>array(0,0,0), + "darkslategray"=>array(47,79,79), + "dimgray"=>array(105,105,105), + "slategray"=>array(112,128,144), + "lightslategray"=>array(119,136,153), + "gray"=>array(190,190,190), + "lightgray"=>array(211,211,211), + "midnightblue"=>array(25,25,112), + "navy"=>array(0,0,128), + "cornflowerblue"=>array(100,149,237), + "darkslateblue"=>array(72,61,139), + "slateblue"=>array(106,90,205), + "mediumslateblue"=>array(123,104,238), + "lightslateblue"=>array(132,112,255), + "mediumblue"=>array(0,0,205), + "royalblue"=>array(65,105,225), + "blue"=>array(0,0,255), + "dodgerblue"=>array(30,144,255), + "deepskyblue"=>array(0,191,255), + "skyblue"=>array(135,206,235), + "lightskyblue"=>array(135,206,250), + "steelblue"=>array(70,130,180), + "lightred"=>array(211,167,168), + "lightsteelblue"=>array(176,196,222), + "lightblue"=>array(173,216,230), + "powderblue"=>array(176,224,230), + "paleturquoise"=>array(175,238,238), + "darkturquoise"=>array(0,206,209), + "mediumturquoise"=>array(72,209,204), + "turquoise"=>array(64,224,208), + "cyan"=>array(0,255,255), + "lightcyan"=>array(224,255,255), + "cadetblue"=>array(95,158,160), + "mediumaquamarine"=>array(102,205,170), + "aquamarine"=>array(127,255,212), + "darkgreen"=>array(0,100,0), + "darkolivegreen"=>array(85,107,47), + "darkseagreen"=>array(143,188,143), + "seagreen"=>array(46,139,87), + "mediumseagreen"=>array(60,179,113), + "lightseagreen"=>array(32,178,170), + "palegreen"=>array(152,251,152), + "springgreen"=>array(0,255,127), + "lawngreen"=>array(124,252,0), + "green"=>array(0,255,0), + "chartreuse"=>array(127,255,0), + "mediumspringgreen"=>array(0,250,154), + "greenyellow"=>array(173,255,47), + "limegreen"=>array(50,205,50), + "yellowgreen"=>array(154,205,50), + "forestgreen"=>array(34,139,34), + "olivedrab"=>array(107,142,35), + "darkkhaki"=>array(189,183,107), + "khaki"=>array(240,230,140), + "palegoldenrod"=>array(238,232,170), + "lightgoldenrodyellow"=>array(250,250,210), + "lightyellow"=>array(255,255,200), + "yellow"=>array(255,255,0), + "gold"=>array(255,215,0), + "lightgoldenrod"=>array(238,221,130), + "goldenrod"=>array(218,165,32), + "darkgoldenrod"=>array(184,134,11), + "rosybrown"=>array(188,143,143), + "indianred"=>array(205,92,92), + "saddlebrown"=>array(139,69,19), + "sienna"=>array(160,82,45), + "peru"=>array(205,133,63), + "burlywood"=>array(222,184,135), + "beige"=>array(245,245,220), + "wheat"=>array(245,222,179), + "sandybrown"=>array(244,164,96), + "tan"=>array(210,180,140), + "chocolate"=>array(210,105,30), + "firebrick"=>array(178,34,34), + "brown"=>array(165,42,42), + "darksalmon"=>array(233,150,122), + "salmon"=>array(250,128,114), + "lightsalmon"=>array(255,160,122), + "orange"=>array(255,165,0), + "darkorange"=>array(255,140,0), + "coral"=>array(255,127,80), + "lightcoral"=>array(240,128,128), + "tomato"=>array(255,99,71), + "orangered"=>array(255,69,0), + "red"=>array(255,0,0), + "hotpink"=>array(255,105,180), + "deeppink"=>array(255,20,147), + "pink"=>array(255,192,203), + "lightpink"=>array(255,182,193), + "palevioletred"=>array(219,112,147), + "maroon"=>array(176,48,96), + "mediumvioletred"=>array(199,21,133), + "violetred"=>array(208,32,144), + "magenta"=>array(255,0,255), + "violet"=>array(238,130,238), + "plum"=>array(221,160,221), + "orchid"=>array(218,112,214), + "mediumorchid"=>array(186,85,211), + "darkorchid"=>array(153,50,204), + "darkviolet"=>array(148,0,211), + "blueviolet"=>array(138,43,226), + "purple"=>array(160,32,240), + "mediumpurple"=>array(147,112,219), + "thistle"=>array(216,191,216), + "snow1"=>array(255,250,250), + "snow2"=>array(238,233,233), + "snow3"=>array(205,201,201), + "snow4"=>array(139,137,137), + "seashell1"=>array(255,245,238), + "seashell2"=>array(238,229,222), + "seashell3"=>array(205,197,191), + "seashell4"=>array(139,134,130), + "AntiqueWhite1"=>array(255,239,219), + "AntiqueWhite2"=>array(238,223,204), + "AntiqueWhite3"=>array(205,192,176), + "AntiqueWhite4"=>array(139,131,120), + "bisque1"=>array(255,228,196), + "bisque2"=>array(238,213,183), + "bisque3"=>array(205,183,158), + "bisque4"=>array(139,125,107), + "peachPuff1"=>array(255,218,185), + "peachpuff2"=>array(238,203,173), + "peachpuff3"=>array(205,175,149), + "peachpuff4"=>array(139,119,101), + "navajowhite1"=>array(255,222,173), + "navajowhite2"=>array(238,207,161), + "navajowhite3"=>array(205,179,139), + "navajowhite4"=>array(139,121,94), + "lemonchiffon1"=>array(255,250,205), + "lemonchiffon2"=>array(238,233,191), + "lemonchiffon3"=>array(205,201,165), + "lemonchiffon4"=>array(139,137,112), + "ivory1"=>array(255,255,240), + "ivory2"=>array(238,238,224), + "ivory3"=>array(205,205,193), + "ivory4"=>array(139,139,131), + "honeydew"=>array(193,205,193), + "lavenderblush1"=>array(255,240,245), + "lavenderblush2"=>array(238,224,229), + "lavenderblush3"=>array(205,193,197), + "lavenderblush4"=>array(139,131,134), + "mistyrose1"=>array(255,228,225), + "mistyrose2"=>array(238,213,210), + "mistyrose3"=>array(205,183,181), + "mistyrose4"=>array(139,125,123), + "azure1"=>array(240,255,255), + "azure2"=>array(224,238,238), + "azure3"=>array(193,205,205), + "azure4"=>array(131,139,139), + "slateblue1"=>array(131,111,255), + "slateblue2"=>array(122,103,238), + "slateblue3"=>array(105,89,205), + "slateblue4"=>array(71,60,139), + "royalblue1"=>array(72,118,255), + "royalblue2"=>array(67,110,238), + "royalblue3"=>array(58,95,205), + "royalblue4"=>array(39,64,139), + "dodgerblue1"=>array(30,144,255), + "dodgerblue2"=>array(28,134,238), + "dodgerblue3"=>array(24,116,205), + "dodgerblue4"=>array(16,78,139), + "steelblue1"=>array(99,184,255), + "steelblue2"=>array(92,172,238), + "steelblue3"=>array(79,148,205), + "steelblue4"=>array(54,100,139), + "deepskyblue1"=>array(0,191,255), + "deepskyblue2"=>array(0,178,238), + "deepskyblue3"=>array(0,154,205), + "deepskyblue4"=>array(0,104,139), + "skyblue1"=>array(135,206,255), + "skyblue2"=>array(126,192,238), + "skyblue3"=>array(108,166,205), + "skyblue4"=>array(74,112,139), + "lightskyblue1"=>array(176,226,255), + "lightskyblue2"=>array(164,211,238), + "lightskyblue3"=>array(141,182,205), + "lightskyblue4"=>array(96,123,139), + "slategray1"=>array(198,226,255), + "slategray2"=>array(185,211,238), + "slategray3"=>array(159,182,205), + "slategray4"=>array(108,123,139), + "lightsteelblue1"=>array(202,225,255), + "lightsteelblue2"=>array(188,210,238), + "lightsteelblue3"=>array(162,181,205), + "lightsteelblue4"=>array(110,123,139), + "lightblue1"=>array(191,239,255), + "lightblue2"=>array(178,223,238), + "lightblue3"=>array(154,192,205), + "lightblue4"=>array(104,131,139), + "lightcyan1"=>array(224,255,255), + "lightcyan2"=>array(209,238,238), + "lightcyan3"=>array(180,205,205), + "lightcyan4"=>array(122,139,139), + "paleturquoise1"=>array(187,255,255), + "paleturquoise2"=>array(174,238,238), + "paleturquoise3"=>array(150,205,205), + "paleturquoise4"=>array(102,139,139), + "cadetblue1"=>array(152,245,255), + "cadetblue2"=>array(142,229,238), + "cadetblue3"=>array(122,197,205), + "cadetblue4"=>array(83,134,139), + "turquoise1"=>array(0,245,255), + "turquoise2"=>array(0,229,238), + "turquoise3"=>array(0,197,205), + "turquoise4"=>array(0,134,139), + "cyan1"=>array(0,255,255), + "cyan2"=>array(0,238,238), + "cyan3"=>array(0,205,205), + "cyan4"=>array(0,139,139), + "darkslategray1"=>array(151,255,255), + "darkslategray2"=>array(141,238,238), + "darkslategray3"=>array(121,205,205), + "darkslategray4"=>array(82,139,139), + "aquamarine1"=>array(127,255,212), + "aquamarine2"=>array(118,238,198), + "aquamarine3"=>array(102,205,170), + "aquamarine4"=>array(69,139,116), + "darkseagreen1"=>array(193,255,193), + "darkseagreen2"=>array(180,238,180), + "darkseagreen3"=>array(155,205,155), + "darkseagreen4"=>array(105,139,105), + "seagreen1"=>array(84,255,159), + "seagreen2"=>array(78,238,148), + "seagreen3"=>array(67,205,128), + "seagreen4"=>array(46,139,87), + "palegreen1"=>array(154,255,154), + "palegreen2"=>array(144,238,144), + "palegreen3"=>array(124,205,124), + "palegreen4"=>array(84,139,84), + "springgreen1"=>array(0,255,127), + "springgreen2"=>array(0,238,118), + "springgreen3"=>array(0,205,102), + "springgreen4"=>array(0,139,69), + "chartreuse1"=>array(127,255,0), + "chartreuse2"=>array(118,238,0), + "chartreuse3"=>array(102,205,0), + "chartreuse4"=>array(69,139,0), + "olivedrab1"=>array(192,255,62), + "olivedrab2"=>array(179,238,58), + "olivedrab3"=>array(154,205,50), + "olivedrab4"=>array(105,139,34), + "darkolivegreen1"=>array(202,255,112), + "darkolivegreen2"=>array(188,238,104), + "darkolivegreen3"=>array(162,205,90), + "darkolivegreen4"=>array(110,139,61), + "khaki1"=>array(255,246,143), + "khaki2"=>array(238,230,133), + "khaki3"=>array(205,198,115), + "khaki4"=>array(139,134,78), + "lightgoldenrod1"=>array(255,236,139), + "lightgoldenrod2"=>array(238,220,130), + "lightgoldenrod3"=>array(205,190,112), + "lightgoldenrod4"=>array(139,129,76), + "yellow1"=>array(255,255,0), + "yellow2"=>array(238,238,0), + "yellow3"=>array(205,205,0), + "yellow4"=>array(139,139,0), + "gold1"=>array(255,215,0), + "gold2"=>array(238,201,0), + "gold3"=>array(205,173,0), + "gold4"=>array(139,117,0), + "goldenrod1"=>array(255,193,37), + "goldenrod2"=>array(238,180,34), + "goldenrod3"=>array(205,155,29), + "goldenrod4"=>array(139,105,20), + "darkgoldenrod1"=>array(255,185,15), + "darkgoldenrod2"=>array(238,173,14), + "darkgoldenrod3"=>array(205,149,12), + "darkgoldenrod4"=>array(139,101,8), + "rosybrown1"=>array(255,193,193), + "rosybrown2"=>array(238,180,180), + "rosybrown3"=>array(205,155,155), + "rosybrown4"=>array(139,105,105), + "indianred1"=>array(255,106,106), + "indianred2"=>array(238,99,99), + "indianred3"=>array(205,85,85), + "indianred4"=>array(139,58,58), + "sienna1"=>array(255,130,71), + "sienna2"=>array(238,121,66), + "sienna3"=>array(205,104,57), + "sienna4"=>array(139,71,38), + "burlywood1"=>array(255,211,155), + "burlywood2"=>array(238,197,145), + "burlywood3"=>array(205,170,125), + "burlywood4"=>array(139,115,85), + "wheat1"=>array(255,231,186), + "wheat2"=>array(238,216,174), + "wheat3"=>array(205,186,150), + "wheat4"=>array(139,126,102), + "tan1"=>array(255,165,79), + "tan2"=>array(238,154,73), + "tan3"=>array(205,133,63), + "tan4"=>array(139,90,43), + "chocolate1"=>array(255,127,36), + "chocolate2"=>array(238,118,33), + "chocolate3"=>array(205,102,29), + "chocolate4"=>array(139,69,19), + "firebrick1"=>array(255,48,48), + "firebrick2"=>array(238,44,44), + "firebrick3"=>array(205,38,38), + "firebrick4"=>array(139,26,26), + "brown1"=>array(255,64,64), + "brown2"=>array(238,59,59), + "brown3"=>array(205,51,51), + "brown4"=>array(139,35,35), + "salmon1"=>array(255,140,105), + "salmon2"=>array(238,130,98), + "salmon3"=>array(205,112,84), + "salmon4"=>array(139,76,57), + "lightsalmon1"=>array(255,160,122), + "lightsalmon2"=>array(238,149,114), + "lightsalmon3"=>array(205,129,98), + "lightsalmon4"=>array(139,87,66), + "orange1"=>array(255,165,0), + "orange2"=>array(238,154,0), + "orange3"=>array(205,133,0), + "orange4"=>array(139,90,0), + "darkorange1"=>array(255,127,0), + "darkorange2"=>array(238,118,0), + "darkorange3"=>array(205,102,0), + "darkorange4"=>array(139,69,0), + "coral1"=>array(255,114,86), + "coral2"=>array(238,106,80), + "coral3"=>array(205,91,69), + "coral4"=>array(139,62,47), + "tomato1"=>array(255,99,71), + "tomato2"=>array(238,92,66), + "tomato3"=>array(205,79,57), + "tomato4"=>array(139,54,38), + "orangered1"=>array(255,69,0), + "orangered2"=>array(238,64,0), + "orangered3"=>array(205,55,0), + "orangered4"=>array(139,37,0), + "deeppink1"=>array(255,20,147), + "deeppink2"=>array(238,18,137), + "deeppink3"=>array(205,16,118), + "deeppink4"=>array(139,10,80), + "hotpink1"=>array(255,110,180), + "hotpink2"=>array(238,106,167), + "hotpink3"=>array(205,96,144), + "hotpink4"=>array(139,58,98), + "pink1"=>array(255,181,197), + "pink2"=>array(238,169,184), + "pink3"=>array(205,145,158), + "pink4"=>array(139,99,108), + "lightpink1"=>array(255,174,185), + "lightpink2"=>array(238,162,173), + "lightpink3"=>array(205,140,149), + "lightpink4"=>array(139,95,101), + "palevioletred1"=>array(255,130,171), + "palevioletred2"=>array(238,121,159), + "palevioletred3"=>array(205,104,137), + "palevioletred4"=>array(139,71,93), + "maroon1"=>array(255,52,179), + "maroon2"=>array(238,48,167), + "maroon3"=>array(205,41,144), + "maroon4"=>array(139,28,98), + "violetred1"=>array(255,62,150), + "violetred2"=>array(238,58,140), + "violetred3"=>array(205,50,120), + "violetred4"=>array(139,34,82), + "magenta1"=>array(255,0,255), + "magenta2"=>array(238,0,238), + "magenta3"=>array(205,0,205), + "magenta4"=>array(139,0,139), + "mediumred"=>array(140,34,34), + "orchid1"=>array(255,131,250), + "orchid2"=>array(238,122,233), + "orchid3"=>array(205,105,201), + "orchid4"=>array(139,71,137), + "plum1"=>array(255,187,255), + "plum2"=>array(238,174,238), + "plum3"=>array(205,150,205), + "plum4"=>array(139,102,139), + "mediumorchid1"=>array(224,102,255), + "mediumorchid2"=>array(209,95,238), + "mediumorchid3"=>array(180,82,205), + "mediumorchid4"=>array(122,55,139), + "darkorchid1"=>array(191,62,255), + "darkorchid2"=>array(178,58,238), + "darkorchid3"=>array(154,50,205), + "darkorchid4"=>array(104,34,139), + "purple1"=>array(155,48,255), + "purple2"=>array(145,44,238), + "purple3"=>array(125,38,205), + "purple4"=>array(85,26,139), + "mediumpurple1"=>array(171,130,255), + "mediumpurple2"=>array(159,121,238), + "mediumpurple3"=>array(137,104,205), + "mediumpurple4"=>array(93,71,139), + "thistle1"=>array(255,225,255), + "thistle2"=>array(238,210,238), + "thistle3"=>array(205,181,205), + "thistle4"=>array(139,123,139), + "gray1"=>array(10,10,10), + "gray2"=>array(40,40,30), + "gray3"=>array(70,70,70), + "gray4"=>array(100,100,100), + "gray5"=>array(130,130,130), + "gray6"=>array(160,160,160), + "gray7"=>array(190,190,190), + "gray8"=>array(210,210,210), + "gray9"=>array(240,240,240), + "darkgray"=>array(100,100,100), + "darkblue"=>array(0,0,139), + "darkcyan"=>array(0,139,139), + "darkmagenta"=>array(139,0,139), + "darkred"=>array(139,0,0), + "silver"=>array(192, 192, 192), + "eggplant"=>array(144,176,168), + "lightgreen"=>array(144,238,144)); + } +//---------------- +// PUBLIC METHODS + // Colors can be specified as either + // 1. #xxxxxx HTML style + // 2. "colorname" as a named color + // 3. array(r,g,b) RGB triple + // This function translates this to a native RGB format and returns an + // RGB triple. + function Color($aColor) { + if (is_string($aColor)) { + // Strip of any alpha factor + $pos = strpos($aColor,'@'); + if( $pos === false ) { + $alpha = 0; + } + else { + $pos2 = strpos($aColor,':'); + if( $pos2===false ) + $pos2 = $pos-1; // Sentinel + if( $pos > $pos2 ) { + $alpha = str_replace(',','.',substr($aColor,$pos+1)); + $aColor = substr($aColor,0,$pos); + } + else { + $alpha = substr($aColor,$pos+1,$pos2-$pos-1); + $aColor = substr($aColor,0,$pos).substr($aColor,$pos2); + } + } + + // Extract potential adjustment figure at end of color + // specification + $pos = strpos($aColor,":"); + if( $pos === false ) { + $adj = 1.0; + } + else { + $adj = 0.0 + str_replace(',','.',substr($aColor,$pos+1)); + $aColor = substr($aColor,0,$pos); + } + if( $adj < 0 ) + JpGraphError::RaiseL(25077);//('Adjustment factor for color must be > 0'); + + if (substr($aColor, 0, 1) == "#") { + $r = hexdec(substr($aColor, 1, 2)); + $g = hexdec(substr($aColor, 3, 2)); + $b = hexdec(substr($aColor, 5, 2)); + } else { + if(!isset($this->rgb_table[$aColor]) ) + JpGraphError::RaiseL(25078,$aColor);//(" Unknown color: $aColor"); + $tmp=$this->rgb_table[$aColor]; + $r = $tmp[0]; + $g = $tmp[1]; + $b = $tmp[2]; + } + // Scale adj so that an adj=2 always + // makes the color 100% white (i.e. 255,255,255. + // and adj=1 neutral and adj=0 black. + if( $adj > 1 ) { + $m = ($adj-1.0)*(255-min(255,min($r,min($g,$b)))); + return array(min(255,$r+$m), min(255,$g+$m), min(255,$b+$m),$alpha); + } + elseif( $adj < 1 ) { + $m = ($adj-1.0)*max(255,max($r,max($g,$b))); + return array(max(0,$r+$m), max(0,$g+$m), max(0,$b+$m),$alpha); + } + else { + return array($r,$g,$b,$alpha); + } + + } elseif( is_array($aColor) ) { + if( count($aColor)==3 ) { + $aColor[3]=0; + return $aColor; + } + else + return $aColor; + } + else + JpGraphError::RaiseL(25079,$aColor,count($aColor));//(" Unknown color specification: $aColor , size=".count($aColor)); + } + + // Compare two colors + // return true if equal + function Equal($aCol1,$aCol2) { + $c1 = $this->Color($aCol1); + $c2 = $this->Color($aCol2); + if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] ) + return true; + else + return false; + } + + // Allocate a new color in the current image + // Return new color index, -1 if no more colors could be allocated + function Allocate($aColor,$aAlpha=0.0) { + list ($r, $g, $b, $a) = $this->color($aColor); + // If alpha is specified in the color string then this + // takes precedence over the second argument + if( $a > 0 ) + $aAlpha = $a; + if( $aAlpha < 0 || $aAlpha > 1 ) { + JpGraphError::RaiseL(25080);//('Alpha parameter for color must be between 0.0 and 1.0'); + } + return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127)); + } +} // Class + + +//=================================================== +// CLASS Legend +// Description: Responsible for drawing the box containing +// all the legend text for the graph +//=================================================== +DEFINE('_DEFAULT_LPM_SIZE',8); +class Legend { + public $txtcol=array(); + private $color=array(0,0,0); // Default fram color + private $fill_color=array(235,235,235); // Default fill color + private $shadow=true; // Shadow around legend "box" + private $shadow_color='darkgray@0.5'; + private $mark_abs_hsize=_DEFAULT_LPM_SIZE,$mark_abs_vsize=_DEFAULT_LPM_SIZE; + private $xmargin=10,$ymargin=3,$shadow_width=2; + private $xlmargin=2, $ylmargin=''; + private $xpos=0.05, $ypos=0.15, $xabspos=-1, $yabspos=-1; + private $halign="right", $valign="top"; + private $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12; + private $font_color='black'; + private $hide=false,$layout_n=1; + private $weight=1,$frameweight=1; + private $csimareas=''; + private $reverse = false ; +//--------------- +// CONSTRUCTOR + function Legend() { + // Empty + } +//--------------- +// PUBLIC METHODS + function Hide($aHide=true) { + $this->hide=$aHide; + } + + function SetHColMargin($aXMarg) { + $this->xmargin = $aXMarg; + } + + function SetVColMargin($aSpacing) { + $this->ymargin = $aSpacing ; + } + + function SetLeftMargin($aXMarg) { + $this->xlmargin = $aXMarg; + } + + + // Synonym + function SetLineSpacing($aSpacing) { + $this->ymargin = $aSpacing ; + } + + function SetShadow($aShow='gray',$aWidth=2) { + if( is_string($aShow) ) { + $this->shadow_color = $aShow; + $this->shadow=true; + } + else + $this->shadow=$aShow; + $this->shadow_width=$aWidth; + } + + function SetMarkAbsSize($aSize) { + $this->mark_abs_vsize = $aSize ; + $this->mark_abs_hsize = $aSize ; + } + + function SetMarkAbsVSize($aSize) { + $this->mark_abs_vsize = $aSize ; + } + + function SetMarkAbsHSize($aSize) { + $this->mark_abs_hsize = $aSize ; + } + + function SetLineWeight($aWeight) { + $this->weight = $aWeight; + } + + function SetFrameWeight($aWeight) { + $this->frameweight = $aWeight; + } + + function SetLayout($aDirection=LEGEND_VERT) { + $this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ; + } + + function SetColumns($aCols) { + $this->layout_n = $aCols ; + } + + function SetReverse($f=true) { + $this->reverse = $f ; + } + + // Set color on frame around box + function SetColor($aFontColor,$aColor='black') { + $this->font_color=$aFontColor; + $this->color=$aColor; + } + + function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) { + $this->font_family = $aFamily; + $this->font_style = $aStyle; + $this->font_size = $aSize; + } + + function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") { + $this->Pos($aX,$aY,$aHAlign,$aVAlign); + } + + function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") { + $this->xabspos=$aX; + $this->yabspos=$aY; + $this->halign=$aHAlign; + $this->valign=$aVAlign; + } + + + function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") { + if( !($aX<1 && $aY<1) ) + JpGraphError::RaiseL(25120);//(" Position for legend must be given as percentage in range 0-1"); + $this->xpos=$aX; + $this->ypos=$aY; + $this->halign=$aHAlign; + $this->valign=$aVAlign; + } + + function SetFillColor($aColor) { + $this->fill_color=$aColor; + } + + function Add($aTxt,$aColor,$aPlotmark='',$aLinestyle=0,$csimtarget='',$csimalt='',$csimwintarget='') { + $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt,$csimwintarget); + } + + function GetCSIMAreas() { + return $this->csimareas; + } + + function Stroke(&$aImg) { + // Constant + $fillBoxFrameWeight=1; + + if( $this->hide ) return; + + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + + if( $this->reverse ) { + $this->txtcol = array_reverse($this->txtcol); + } + + $n=count($this->txtcol); + if( $n == 0 ) return; + + // Find out the max width and height of each column to be able + // to size the legend box. + $numcolumns = ($n > $this->layout_n ? $this->layout_n : $n); + for( $i=0; $i < $numcolumns; ++$i ) { + $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) + + 2*$this->xmargin + 2*$this->mark_abs_hsize; + $colheight[$i] = 0; + } + + // Find our maximum height in each row + $rows = 0 ; $rowheight[0] = 0; + for( $i=0; $i < $n; ++$i ) { + $h = max($this->mark_abs_vsize,$aImg->GetTextHeight($this->txtcol[$i][0]))+$this->ymargin; + if( $i % $numcolumns == 0 ) { + $rows++; + $rowheight[$rows-1] = 0; + } + $rowheight[$rows-1] = max($rowheight[$rows-1],$h); + } + + $abs_height = 0; + for( $i=0; $i < $rows; ++$i ) { + $abs_height += $rowheight[$i] ; + } + + // Make sure that the height is at least as high as mark size + ymargin + $abs_height = max($abs_height,$this->mark_abs_vsize); + + // We add 3 extra pixels height to compensate for the difficult in + // calculating font height + $abs_height += $this->ymargin+3; + + // Find out the maximum width in each column + for( $i=$numcolumns; $i < $n; ++$i ) { + $colwidth[$i % $numcolumns] = max( + $aImg->GetTextWidth($this->txtcol[$i][0])+2*$this->xmargin+2*$this->mark_abs_hsize,$colwidth[$i % $numcolumns]); + } + + // Get the total width + $mtw = 0; + for( $i=0; $i < $numcolumns; ++$i ) { + $mtw += $colwidth[$i] ; + } + + // Find out maximum width we need for legend box + $abs_width = $mtw+$this->xlmargin; + + if( $this->xabspos === -1 && $this->yabspos === -1 ) { + $this->xabspos = $this->xpos*$aImg->width ; + $this->yabspos = $this->ypos*$aImg->height ; + } + + // Positioning of the legend box + if( $this->halign == 'left' ) + $xp = $this->xabspos; + elseif( $this->halign == 'center' ) + $xp = $this->xabspos - $abs_width/2; + else + $xp = $aImg->width - $this->xabspos - $abs_width; + + $yp=$this->yabspos; + if( $this->valign == 'center' ) + $yp-=$abs_height/2; + elseif( $this->valign == 'bottom' ) + $yp-=$abs_height; + + // Stroke legend box + $aImg->SetColor($this->color); + $aImg->SetLineWeight($this->frameweight); + $aImg->SetLineStyle('solid'); + + if( $this->shadow ) + $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width, + $yp+$abs_height+$this->shadow_width, + $this->fill_color,$this->shadow_width,$this->shadow_color); + else { + $aImg->SetColor($this->fill_color); + $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height); + $aImg->SetColor($this->color); + $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height); + } + + // x1,y1 is the position for the legend mark + $x1=$xp+$this->mark_abs_hsize+$this->xlmargin; + $y1=$yp + $this->ymargin; + + $f2 = round($aImg->GetTextHeight('X')/2); + + $grad = new Gradient($aImg); + $patternFactory = null; + + // Now stroke each legend in turn + // Each plot has added the following information to the legend + // p[0] = Legend text + // p[1] = Color, + // p[2] = For markers a reference to the PlotMark object + // p[3] = For lines the line style, for gradient the negative gradient style + // p[4] = CSIM target + // p[5] = CSIM Alt text + $i = 1 ; $row = 0; + foreach($this->txtcol as $p) { + + // STROKE DEBUG BOX + if( _JPG_DEBUG ) { + $aImg->SetLineWeight(1); + $aImg->SetColor('red'); + $aImg->SetLineStyle('solid'); + $aImg->Rectangle($xp,$y1,$xp+$abs_width,$y1+$rowheight[$row]); + } + + $aImg->SetLineWeight($this->weight); + $x1 = round($x1); $y1=round($y1); + if ( !empty($p[2]) && $p[2]->GetType() > -1 ) { + // Make a plot mark legend + $aImg->SetColor($p[1]); + if( is_string($p[3]) || $p[3]>0 ) { + $aImg->SetLineStyle($p[3]); + $aImg->StyleLine($x1-$this->mark_abs_hsize,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2); + } + // Stroke a mark with the standard size + // (As long as it is not an image mark ) + if( $p[2]->GetType() != MARK_IMG ) { + + // Clear any user callbacks since we ont want them called for + // the legend marks + $p[2]->iFormatCallback = ''; + $p[2]->iFormatCallback2 = ''; + + // Since size for circles is specified as the radius + // this means that we must half the size to make the total + // width behave as the other marks + if( $p[2]->GetType() == MARK_FILLEDCIRCLE || $p[2]->GetType() == MARK_CIRCLE ) { + $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)/2); + $p[2]->Stroke($aImg,$x1,$y1+$f2); + } + else { + $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)); + $p[2]->Stroke($aImg,$x1,$y1+$f2); + } + } + } + elseif ( !empty($p[2]) && (is_string($p[3]) || $p[3]>0 ) ) { + // Draw a styled line + $aImg->SetColor($p[1]); + $aImg->SetLineStyle($p[3]); + $aImg->StyleLine($x1-1,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2); + $aImg->StyleLine($x1-1,$y1+$f2+1,$x1+$this->mark_abs_hsize,$y1+$f2+1); + } + else { + // Draw a colored box + $color = $p[1] ; + // We make boxes slightly larger to better show + $boxsize = min($this->mark_abs_vsize,$this->mark_abs_hsize) + 2 ; + $ym = round($y1 + $f2 - $boxsize/2); + // We either need to plot a gradient or a + // pattern. To differentiate we use a kludge. + // Patterns have a p[3] value of < -100 + if( $p[3] < -100 ) { + // p[1][0] == iPattern, p[1][1] == iPatternColor, p[1][2] == iPatternDensity + if( $patternFactory == null ) { + $patternFactory = new RectPatternFactory(); + } + $prect = $patternFactory->Create($p[1][0],$p[1][1],1); + $prect->SetBackground($p[1][3]); + $prect->SetDensity($p[1][2]+1); + $prect->SetPos(new Rectangle($x1,$ym,$boxsize,$boxsize)); + $prect->Stroke($aImg); + $prect=null; + } + else { + if( is_array($color) && count($color)==2 ) { + // The client want a gradient color + $grad->FilledRectangle($x1,$ym, + $x1+$boxsize,$ym+$boxsize, + $color[0],$color[1],-$p[3]); + } + else { + $aImg->SetColor($p[1]); + $aImg->FilledRectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize); + } + $aImg->SetColor($this->color); + $aImg->SetLineWeight($fillBoxFrameWeight); + $aImg->Rectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize); + } + } + $aImg->SetColor($this->font_color); + $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); + $aImg->SetTextAlign("left","top"); + $aImg->StrokeText(round($x1+$this->mark_abs_hsize+$this->xmargin),$y1,$p[0]); + + // Add CSIM for Legend if defined + if( !empty($p[4]) ) { + + $xe = $x1 + $this->xmargin+$this->mark_abs_hsize+$aImg->GetTextWidth($p[0]); + $ye = $y1 + max($this->mark_abs_vsize,$aImg->GetTextHeight($p[0])); + $coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye"; + if( ! empty($p[4]) ) { + $this->csimareas .= "csimareas .= " target=\"".$p[6]."\""; + } + + if( !empty($p[5]) ) { + $tmp=sprintf($p[5],$p[0]); + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + } + if( $i >= $this->layout_n ) { + $x1 = $xp+$this->mark_abs_hsize+$this->xlmargin; + $y1 += $rowheight[$row++]; + $i = 1; + } + else { + $x1 += $colwidth[($i-1) % $numcolumns] ; + ++$i; + } + } + } +} // Class + + +//=================================================== +// CLASS DisplayValue +// Description: Used to print data values at data points +//=================================================== +class DisplayValue { + public $margin=5; + public $show=false; + public $valign="",$halign="center"; + public $format="%.1f",$negformat=""; + private $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10; + private $iFormCallback=''; + private $angle=0; + private $color="navy",$negcolor=""; + private $iHideZero=false; + + function Show($aFlag=true) { + $this->show=$aFlag; + } + + function SetColor($aColor,$aNegcolor="") { + $this->color = $aColor; + $this->negcolor = $aNegcolor; + } + + function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) { + $this->ff=$aFontFamily; + $this->fs=$aFontStyle; + $this->fsize=$aFontSize; + } + + function ApplyFont($aImg) { + $aImg->SetFont($this->ff,$this->fs,$this->fsize); + } + + function SetMargin($aMargin) { + $this->margin = $aMargin; + } + + function SetAngle($aAngle) { + $this->angle = $aAngle; + } + + function SetAlign($aHAlign,$aVAlign='') { + $this->halign = $aHAlign; + $this->valign = $aVAlign; + } + + function SetFormat($aFormat,$aNegFormat="") { + $this->format= $aFormat; + $this->negformat= $aNegFormat; + } + + function SetFormatCallback($aFunc) { + $this->iFormCallback = $aFunc; + } + + function HideZero($aFlag=true) { + $this->iHideZero=$aFlag; + } + + function Stroke($img,$aVal,$x,$y) { + + if( $this->show ) + { + if( $this->negformat=="" ) $this->negformat=$this->format; + if( $this->negcolor=="" ) $this->negcolor=$this->color; + + if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) ) + return; + + if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) { + return; + } + + // Since the value is used in different cirumstances we need to check what + // kind of formatting we shall use. For example, to display values in a line + // graph we simply display the formatted value, but in the case where the user + // has already specified a text string we don't fo anything. + if( $this->iFormCallback != '' ) { + $f = $this->iFormCallback; + $sval = call_user_func($f,$aVal); + } + elseif( is_numeric($aVal) ) { + if( $aVal >= 0 ) + $sval=sprintf($this->format,$aVal); + else + $sval=sprintf($this->negformat,$aVal); + } + else + $sval=$aVal; + + $y = $y-sign($aVal)*$this->margin; + + $txt = new Text($sval,$x,$y); + $txt->SetFont($this->ff,$this->fs,$this->fsize); + if( $this->valign == "" ) { + if( $aVal >= 0 ) + $valign = "bottom"; + else + $valign = "top"; + } + else + $valign = $this->valign; + $txt->Align($this->halign,$valign); + + $txt->SetOrientation($this->angle); + if( $aVal > 0 ) + $txt->SetColor($this->color); + else + $txt->SetColor($this->negcolor); + $txt->Stroke($img); + } + } +} + +//=================================================== +// CLASS Plot +// Description: Abstract base class for all concrete plot classes +//=================================================== +class Plot { + public $numpoints=0; + public $value; + public $legend=''; + public $coords=array(); + public $color="black"; + public $hidelegend=false; + public $line_weight=1; + public $csimtargets=array(),$csimwintargets=array(); // Array of targets for CSIM + public $csimareas=""; // Resultant CSIM area tags + public $csimalts=null; // ALT:s for corresponding target + public $legendcsimtarget='',$legendcsimwintarget=''; + public $legendcsimalt=''; + protected $weight=1; + protected $center=false; +//--------------- +// CONSTRUCTOR + function Plot($aDatay,$aDatax=false) { + $this->numpoints = count($aDatay); + if( $this->numpoints==0 ) + JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point."); + $this->coords[0]=$aDatay; + if( is_array($aDatax) ) { + $this->coords[1]=$aDatax; + $n = count($aDatax); + for($i=0; $i < $n; ++$i ) { + if( !is_numeric($aDatax[$i]) ) { + JpGraphError::RaiseL(25070); + } + } + } + $this->value = new DisplayValue(); + } + +//--------------- +// PUBLIC METHODS + + // Stroke the plot + // "virtual" function which must be implemented by + // the subclasses + function Stroke($aImg,$aXScale,$aYScale) { + JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot"); + } + + function HideLegend($f=true) { + $this->hidelegend = $f; + } + + function DoLegend($graph) { + if( !$this->hidelegend ) + $this->Legend($graph); + } + + function StrokeDataValue($img,$aVal,$x,$y) { + $this->value->Stroke($img,$aVal,$x,$y); + } + + // Set href targets for CSIM + function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') { + $this->csimtargets=$aTargets; + $this->csimwintargets=$aWinTargets; + $this->csimalts=$aAlts; + } + + // Get all created areas + function GetCSIMareas() { + return $this->csimareas; + } + + // "Virtual" function which gets called before any scale + // or axis are stroked used to do any plot specific adjustment + function PreStrokeAdjust($aGraph) { + if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) ) + JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead."); + return true; + } + + // Get minimum values in plot + function Min() { + if( isset($this->coords[1]) ) + $x=$this->coords[1]; + else + $x=""; + if( $x != "" && count($x) > 0 ) { + $xm=min($x); + } + else + $xm=0; + $y=$this->coords[0]; + $cnt = count($y); + if( $cnt > 0 ) { + /* + if( ! isset($y[0]) ) { + JpGraphError('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)'); + } + $ym = $y[0]; + */ + $i=0; + while( $i<$cnt && !is_numeric($ym=$y[$i]) ) + $i++; + while( $i < $cnt) { + if( is_numeric($y[$i]) ) + $ym=min($ym,$y[$i]); + ++$i; + } + } + else + $ym=""; + return array($xm,$ym); + } + + // Get maximum value in plot + function Max() { + if( isset($this->coords[1]) ) + $x=$this->coords[1]; + else + $x=""; + + if( $x!="" && count($x) > 0 ) + $xm=max($x); + else { + $xm = $this->numpoints-1; + } + $y=$this->coords[0]; + if( count($y) > 0 ) { + /* + if( !isset($y[0]) ) { + JpGraphError::Raise('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)'); +// $y[0] = 0; +// Change in 1.5.1 Don't treat this as an error any more. Just silently convert to 0 +// Change in 1.17 Treat his as an error again !! This is the right way to do !! + } + */ + $cnt = count($y); + $i=0; + while( $i<$cnt && !is_numeric($ym=$y[$i]) ) + $i++; + while( $i < $cnt ) { + if( is_numeric($y[$i]) ) + $ym=max($ym,$y[$i]); + ++$i; + } + } + else + $ym=""; + return array($xm,$ym); + } + + function SetColor($aColor) { + $this->color=$aColor; + } + + function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') { + $this->legend = $aLegend; + $this->legendcsimtarget = $aCSIM; + $this->legendcsimwintarget = $aCSIMWinTarget; + $this->legendcsimalt = $aCSIMAlt; + } + + function SetWeight($aWeight) { + $this->weight=$aWeight; + } + + function SetLineWeight($aWeight=1) { + $this->line_weight=$aWeight; + } + + function SetCenter($aCenter=true) { + $this->center = $aCenter; + } + + // This method gets called by Graph class to plot anything that should go + // into the margin after the margin color has been set. + function StrokeMargin($aImg) { + return true; + } + + // Framework function the chance for each plot class to set a legend + function Legend($aGraph) { + if( $this->legend != "" ) + $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget, + $this->legendcsimalt,$this->legendcsimwintarget); + } + +} // Class + + +//=================================================== +// CLASS PlotLine +// Description: +// Data container class to hold properties for a static +// line that is drawn directly in the plot area. +// Usefull to add static borders inside a plot to show +// for example set-values +//=================================================== +class PlotLine { + public $scaleposition, $direction=-1; + protected $weight=1; + protected $color="black"; + private $legend='',$hidelegend=false, $legendcsimtarget='', $legendcsimalt='',$legendcsimwintarget=''; + private $iLineStyle='solid'; + +//--------------- +// CONSTRUCTOR + function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) { + $this->direction = $aDir; + $this->color=$aColor; + $this->weight=$aWeight; + $this->scaleposition=$aPos; + } + +//--------------- +// PUBLIC METHODS + + function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') { + $this->legend = $aLegend; + $this->legendcsimtarget = $aCSIM; + $this->legendcsimwintarget = $aCSIMWinTarget; + $this->legendcsimalt = $aCSIMAlt; + } + + function HideLegend($f=true) { + $this->hidelegend = $f; + } + + function SetPosition($aScalePosition) { + $this->scaleposition=$aScalePosition; + } + + function SetDirection($aDir) { + $this->direction = $aDir; + } + + function SetColor($aColor) { + $this->color=$aColor; + } + + function SetWeight($aWeight) { + $this->weight=$aWeight; + } + + function SetLineStyle($aStyle) { + $this->iLineStyle = $aStyle; + } + +//--------------- +// PRIVATE METHODS + + function DoLegend(&$graph) { + if( !$this->hidelegend ) + $this->Legend($graph); + } + + // Framework function the chance for each plot class to set a legend + function Legend(&$aGraph) { + if( $this->legend != "" ) { + $dummyPlotMark = new PlotMark(); + $lineStyle = 1; + $aGraph->legend->Add($this->legend,$this->color,$dummyPlotMark,$lineStyle, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + } + + function PreStrokeAdjust($aGraph) { + // Nothing to do + } + + function Stroke($aImg,$aXScale,$aYScale) { + $aImg->SetColor($this->color); + $aImg->SetLineWeight($this->weight); + $oldStyle = $aImg->SetLineStyle($this->iLineStyle); + if( $this->direction == VERTICAL ) { + $ymin_abs=$aYScale->Translate($aYScale->GetMinVal()); + $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal()); + $xpos_abs=$aXScale->Translate($this->scaleposition); + $aImg->StyleLine($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs); + } + elseif( $this->direction == HORIZONTAL ) { + $xmin_abs=$aXScale->Translate($aXScale->GetMinVal()); + $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal()); + $ypos_abs=$aYScale->Translate($this->scaleposition); + $aImg->StyleLine($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs); + } + else { + JpGraphError::RaiseL(25125);//(" Illegal direction for static line"); + } + $aImg->SetLineStyle($oldStyle); + } +} + +// +?> diff --git a/src/classes/jpgraph/jpgraph_bar.php b/src/classes/jpgraph/jpgraph_bar.php new file mode 100644 index 0000000..bcf6fdd --- /dev/null +++ b/src/classes/jpgraph/jpgraph_bar.php @@ -0,0 +1,1009 @@ +Plot($datay,$datax); + ++$this->numpoints; + } + +//--------------- +// PUBLIC METHODS + + // Set a drop shadow for the bar (or rather an "up-right" shadow) + function SetShadow($color="black",$hsize=3,$vsize=3,$show=true) { + $this->bar_shadow=$show; + $this->bar_shadow_color=$color; + $this->bar_shadow_vsize=$vsize; + $this->bar_shadow_hsize=$hsize; + + // Adjust the value margin to compensate for shadow + $this->value->margin += $vsize; + } + + // DEPRECATED use SetYBase instead + function SetYMin($aYStartValue) { + //die("JpGraph Error: Deprecated function SetYMin. Use SetYBase() instead."); + $this->ybase=$aYStartValue; + } + + // Specify the base value for the bars + function SetYBase($aYStartValue) { + $this->ybase=$aYStartValue; + } + + function Legend($graph) { + if( $this->grad && $this->legend!="" && !$this->fill ) { + $color=array($this->grad_fromcolor,$this->grad_tocolor); + // In order to differentiate between gradients and cooors specified as an RGB triple + $graph->legend->Add($this->legend,$color,"",-$this->grad_style, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + elseif( $this->legend!="" && ($this->iPattern > -1 || is_array($this->iPattern)) ) { + if( is_array($this->iPattern) ) { + $p1 = $this->iPattern[0]; + $p2 = $this->iPatternColor[0]; + $p3 = $this->iPatternDensity[0]; + } + else { + $p1 = $this->iPattern; + $p2 = $this->iPatternColor; + $p3 = $this->iPatternDensity; + } + $color = array($p1,$p2,$p3,$this->fill_color); + // A kludge: Too mark that we add a pattern we use a type value of < 100 + $graph->legend->Add($this->legend,$color,"",-101, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + elseif( $this->fill_color && $this->legend!="" ) { + if( is_array($this->fill_color) ) { + $graph->legend->Add($this->legend,$this->fill_color[0],"",0, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + else { + $graph->legend->Add($this->legend,$this->fill_color,"",0, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + } + } + + // Gets called before any axis are stroked + function PreStrokeAdjust($graph) { + parent::PreStrokeAdjust($graph); + + // If we are using a log Y-scale we want the base to be at the + // minimum Y-value unless the user have specifically set some other + // value than the default. + if( substr($graph->axtype,-3,3)=="log" && $this->ybase==0 ) + $this->ybase = $graph->yaxis->scale->GetMinVal(); + + // For a "text" X-axis scale we will adjust the + // display of the bars a little bit. + if( substr($graph->axtype,0,3)=="tex" ) { + // Position the ticks between the bars + $graph->xaxis->scale->ticks->SetXLabelOffset(0.5,0); + + // Center the bars + if( $this->abswidth > -1 ) { + $graph->SetTextScaleAbsCenterOff($this->abswidth); + } + else { + if( $this->align == "center" ) + $graph->SetTextScaleOff(0.5-$this->width/2); + elseif( $this->align == "right" ) + $graph->SetTextScaleOff(1-$this->width); + } + } + elseif( ($this instanceof AccBarPlot) || ($this instanceof GroupBarPlot) ) { + // We only set an absolute width for linear and int scale + // for text scale the width will be set to a fraction of + // the majstep width. + if( $this->abswidth == -1 ) { + // Not set + // set width to a visuable sensible default + $this->abswidth = $graph->img->plotwidth/(2*count($this->coords[0])); + } + } + } + + function Min() { + $m = parent::Min(); + if( $m[1] >= $this->ybase ) + $m[1] = $this->ybase; + return $m; + } + + function Max() { + $m = parent::Max(); + if( $m[1] <= $this->ybase ) + $m[1] = $this->ybase; + return $m; + } + + // Specify width as fractions of the major stepo size + function SetWidth($aWidth) { + if( $aWidth > 1 ) { + // Interpret this as absolute width + $this->abswidth=$aWidth; + } + else + $this->width=$aWidth; + } + + // Specify width in absolute pixels. If specified this + // overrides SetWidth() + function SetAbsWidth($aWidth) { + $this->abswidth=$aWidth; + } + + function SetAlign($aAlign) { + $this->align=$aAlign; + } + + function SetNoFill() { + $this->grad = false; + $this->fill_color=false; + $this->fill=false; + } + + function SetFillColor($aColor) { + $this->fill = true ; + $this->fill_color=$aColor; + } + + function SetFillGradient($aFromColor,$aToColor=null,$aStyle=null) { + $this->grad = true; + $this->grad_fromcolor = $aFromColor; + $this->grad_tocolor = $aToColor; + $this->grad_style = $aStyle; + } + + function SetValuePos($aPos) { + $this->valuepos = $aPos; + } + + function SetPattern($aPattern, $aColor='black'){ + if( is_array($aPattern) ) { + $n = count($aPattern); + $this->iPattern = array(); + $this->iPatternDensity = array(); + if( is_array($aColor) ) { + $this->iPatternColor = array(); + if( count($aColor) != $n ) { + JpGraphError::Raise('NUmber of colors is not the same as the number of patterns in BarPlot::SetPattern()'); + } + } + else + $this->iPatternColor = $aColor; + for( $i=0; $i < $n; ++$i ) { + $this->_SetPatternHelper($aPattern[$i], $this->iPattern[$i], $this->iPatternDensity[$i]); + if( is_array($aColor) ) { + $this->iPatternColor[$i] = $aColor[$i]; + } + } + } + else { + $this->_SetPatternHelper($aPattern, $this->iPattern, $this->iPatternDensity); + $this->iPatternColor = $aColor; + } + } + + function _SetPatternHelper($aPattern, &$aPatternValue, &$aDensity){ + switch( $aPattern ) { + case PATTERN_DIAG1: + $aPatternValue= 1; + $aDensity = 90; + break; + case PATTERN_DIAG2: + $aPatternValue= 1; + $aDensity = 75; + break; + case PATTERN_DIAG3: + $aPatternValue= 2; + $aDensity = 90; + break; + case PATTERN_DIAG4: + $aPatternValue= 2; + $aDensity = 75; + break; + case PATTERN_CROSS1: + $aPatternValue= 8; + $aDensity = 90; + break; + case PATTERN_CROSS2: + $aPatternValue= 8; + $aDensity = 78; + break; + case PATTERN_CROSS3: + $aPatternValue= 8; + $aDensity = 65; + break; + case PATTERN_CROSS4: + $aPatternValue= 7; + $aDensity = 90; + break; + case PATTERN_STRIPE1: + $aPatternValue= 5; + $aDensity = 90; + break; + case PATTERN_STRIPE2: + $aPatternValue= 5; + $aDensity = 75; + break; + default: + JpGraphError::Raise('Unknown pattern specified in call to BarPlot::SetPattern()'); + } + } + + function Stroke($img,$xscale,$yscale) { + + $numpoints = count($this->coords[0]); + if( isset($this->coords[1]) ) { + if( count($this->coords[1])!=$numpoints ) + JpGraphError::Raise("Number of X and Y points are not equal. Number of X-points:".count($this->coords[1])."Number of Y-points:$numpoints"); + else + $exist_x = true; + } + else + $exist_x = false; + + + $numbars=count($this->coords[0]); + + // Use GetMinVal() instead of scale[0] directly since in the case + // of log scale we get a correct value. Log scales will have negative + // values for values < 1 while still not representing negative numbers. + if( $yscale->GetMinVal() >= 0 ) + $zp=$yscale->scale_abs[0]; + else { + $zp=$yscale->Translate(0); + } + + if( $this->abswidth > -1 ) { + $abswidth=$this->abswidth; + } + else + $abswidth=round($this->width*$xscale->scale_factor,0); + + // Count pontetial pattern array to avoid doing the count for each iteration + if( is_array($this->iPattern) ) { + $np = count($this->iPattern); + } + + $grad = null; + for($i=0; $i < $numbars; ++$i) { + + // If value is NULL, or 0 then don't draw a bar at all + if ($this->coords[0][$i] === null || $this->coords[0][$i] === '' ) + continue; + + if( $exist_x ) $x=$this->coords[1][$i]; + else $x=$i; + + $x=$xscale->Translate($x); + +// Comment Note: This confuses the positioning when using acc together with +// grouped bars. Workaround for fixing #191 +/* + if( !$xscale->textscale ) { + if($this->align=="center") + $x -= $abswidth/2; + elseif($this->align=="right") + $x -= $abswidth; + } +*/ + // Stroke fill color and fill gradient + $pts=array( + $x,$zp, + $x,$yscale->Translate($this->coords[0][$i]), + $x+$abswidth,$yscale->Translate($this->coords[0][$i]), + $x+$abswidth,$zp); + if( $this->grad ) { + if( $grad === null ) + $grad = new Gradient($img); + if( is_array($this->grad_fromcolor) ) { + // The first argument (grad_fromcolor) can be either an array or a single color. If it is an array + // then we have two choices. It can either a) be a single color specified as an RGB triple or it can be + // an array to specify both (from, to style) for each individual bar. The way to know the difference is + // to investgate the first element. If this element is an integer [0,255] then we assume it is an RGB + // triple. + $ng = count($this->grad_fromcolor); + if( $ng === 3 ) { + if( is_numeric($this->grad_fromcolor[0]) && $this->grad_fromcolor[0] > 0 && $this->grad_fromcolor[0] < 256 ) { + // RGB Triple + $fromcolor = $this->grad_fromcolor; + $tocolor = $this->grad_tocolor; + $style = $this->grad_style; + } + } + else { + $fromcolor = $this->grad_fromcolor[$i % $ng][0]; + $tocolor = $this->grad_fromcolor[$i % $ng][1]; + $style = $this->grad_fromcolor[$i % $ng][2]; + } + $grad->FilledRectangle($pts[2],$pts[3], + $pts[6],$pts[7], + $fromcolor,$tocolor,$style); + } + else { + $grad->FilledRectangle($pts[2],$pts[3], + $pts[6],$pts[7], + $this->grad_fromcolor,$this->grad_tocolor,$this->grad_style); + } + } + elseif( !empty($this->fill_color) ) { + if(is_array($this->fill_color)) { + $img->PushColor($this->fill_color[$i % count($this->fill_color)]); + } else { + $img->PushColor($this->fill_color); + } + $img->FilledPolygon($pts); + $img->PopColor(); + } + + + // Remember value of this bar + $val=$this->coords[0][$i]; + + if( !empty($val) && !is_numeric($val) ) { + JpGraphError::Raise('All values for a barplot must be numeric. You have specified value['.$i.'] == \''.$val.'\''); + } + + // Determine the shadow + if( $this->bar_shadow && $val != 0) { + + $ssh = $this->bar_shadow_hsize; + $ssv = $this->bar_shadow_vsize; + // Create points to create a "upper-right" shadow + if( $val > 0 ) { + $sp[0]=$pts[6]; $sp[1]=$pts[7]; + $sp[2]=$pts[4]; $sp[3]=$pts[5]; + $sp[4]=$pts[2]; $sp[5]=$pts[3]; + $sp[6]=$pts[2]+$ssh; $sp[7]=$pts[3]-$ssv; + $sp[8]=$pts[4]+$ssh; $sp[9]=$pts[5]-$ssv; + $sp[10]=$pts[6]+$ssh; $sp[11]=$pts[7]-$ssv; + } + elseif( $val < 0 ) { + $sp[0]=$pts[4]; $sp[1]=$pts[5]; + $sp[2]=$pts[6]; $sp[3]=$pts[7]; + $sp[4]=$pts[0]; $sp[5]=$pts[1]; + $sp[6]=$pts[0]+$ssh; $sp[7]=$pts[1]-$ssv; + $sp[8]=$pts[6]+$ssh; $sp[9]=$pts[7]-$ssv; + $sp[10]=$pts[4]+$ssh; $sp[11]=$pts[5]-$ssv; + } + if( is_array($this->bar_shadow_color) ) { + $numcolors = count($this->bar_shadow_color); + if( $numcolors == 0 ) { + JpGraphError::Raise('You have specified an empty array for shadow colors in the bar plot.'); + } + $img->PushColor($this->bar_shadow_color[$i % $numcolors]); + } + else { + $img->PushColor($this->bar_shadow_color); + } + $img->FilledPolygon($sp); + $img->PopColor(); + } + + // Stroke the pattern + if( is_array($this->iPattern) ) { + $f = new RectPatternFactory(); + if( is_array($this->iPatternColor) ) { + $pcolor = $this->iPatternColor[$i % $np]; + } + else + $pcolor = $this->iPatternColor; + $prect = $f->Create($this->iPattern[$i % $np],$pcolor,1); + $prect->SetDensity($this->iPatternDensity[$i % $np]); + + if( $val < 0 ) { + $rx = $pts[0]; + $ry = $pts[1]; + } + else { + $rx = $pts[2]; + $ry = $pts[3]; + } + $width = abs($pts[4]-$pts[0])+1; + $height = abs($pts[1]-$pts[3])+1; + $prect->SetPos(new Rectangle($rx,$ry,$width,$height)); + $prect->Stroke($img); + } + else { + if( $this->iPattern > -1 ) { + $f = new RectPatternFactory(); + $prect = $f->Create($this->iPattern,$this->iPatternColor,1); + $prect->SetDensity($this->iPatternDensity); + if( $val < 0 ) { + $rx = $pts[0]; + $ry = $pts[1]; + } + else { + $rx = $pts[2]; + $ry = $pts[3]; + } + $width = abs($pts[4]-$pts[0])+1; + $height = abs($pts[1]-$pts[3])+1; + $prect->SetPos(new Rectangle($rx,$ry,$width,$height)); + $prect->Stroke($img); + } + } + // Stroke the outline of the bar + if( is_array($this->color) ) + $img->SetColor($this->color[$i % count($this->color)]); + else + $img->SetColor($this->color); + + $pts[] = $pts[0]; + $pts[] = $pts[1]; + + if( $this->weight > 0 ) { + $img->SetLineWeight($this->weight); + $img->Polygon($pts); + } + + // Determine how to best position the values of the individual bars + $x=$pts[2]+($pts[4]-$pts[2])/2; + if( $this->valuepos=='top' ) { + $y=$pts[3]; + if( $img->a === 90 ) { + if( $val < 0 ) + $this->value->SetAlign('right','center'); + else + $this->value->SetAlign('left','center'); + + } + $this->value->Stroke($img,$val,$x,$y); + } + elseif( $this->valuepos=='max' ) { + $y=$pts[3]; + if( $img->a === 90 ) { + if( $val < 0 ) + $this->value->SetAlign('left','center'); + else + $this->value->SetAlign('right','center'); + } + else { + $this->value->SetAlign('center','top'); + } + $this->value->SetMargin(-3); + $this->value->Stroke($img,$val,$x,$y); + } + elseif( $this->valuepos=='center' ) { + $y = ($pts[3] + $pts[1])/2; + $this->value->SetAlign('center','center'); + $this->value->SetMargin(0); + $this->value->Stroke($img,$val,$x,$y); + } + elseif( $this->valuepos=='bottom' || $this->valuepos=='min' ) { + $y=$pts[1]; + if( $img->a === 90 ) { + if( $val < 0 ) + $this->value->SetAlign('right','center'); + else + $this->value->SetAlign('left','center'); + } + $this->value->SetMargin(3); + $this->value->Stroke($img,$val,$x,$y); + } + else { + JpGraphError::Raise('Unknown position for values on bars :'.$this->valuepos); + } + // Create the client side image map + $rpts = $img->ArrRotate($pts); + $csimcoord=round($rpts[0]).", ".round($rpts[1]); + for( $j=1; $j < 4; ++$j){ + $csimcoord .= ", ".round($rpts[2*$j]).", ".round($rpts[2*$j+1]); + } + if( !empty($this->csimtargets[$i]) ) { + $this->csimareas .= 'csimareas .= " href=\"".htmlentities($this->csimtargets[$i])."\""; + + if( !empty($this->csimwintargets[$i]) ) { + $this->csimareas .= " target=\"".$this->csimwintargets[$i]."\" "; + } + + $sval=''; + if( !empty($this->csimalts[$i]) ) { + $sval=sprintf($this->csimalts[$i],$this->coords[0][$i]); + $this->csimareas .= " title=\"$sval\" alt=\"$sval\" "; + } + $this->csimareas .= " />\n"; + } + } + return true; + } +} // Class + +//=================================================== +// CLASS GroupBarPlot +// Description: Produce grouped bar plots +//=================================================== +class GroupBarPlot extends BarPlot { + private $plots, $nbrplots=0; +//--------------- +// CONSTRUCTOR + function GroupBarPlot($plots) { + $this->width=0.7; + $this->plots = $plots; + $this->nbrplots = count($plots); + if( $this->nbrplots < 1 ) { + JpGraphError::Raise('Cannot create GroupBarPlot from empty plot array.'); + } + for($i=0; $i < $this->nbrplots; ++$i ) { + if( empty($this->plots[$i]) || !isset($this->plots[$i]) ) { + JpGraphError::Raise("Group bar plot element nbr $i is undefined or empty."); + } + } + $this->numpoints = $plots[0]->numpoints; + $this->width=0.7; + } + +//--------------- +// PUBLIC METHODS + function Legend($graph) { + $n = count($this->plots); + for($i=0; $i < $n; ++$i) { + $c = get_class($this->plots[$i]); + if( !($this->plots[$i] instanceof BarPlot) ) { + JpGraphError::Raise('One of the objects submitted to GroupBar is not a BarPlot. Make sure that you create the Group Bar plot from an array of BarPlot or AccBarPlot objects. (Class = '.$c.')'); + } + $this->plots[$i]->DoLegend($graph); + } + } + + function Min() { + list($xmin,$ymin) = $this->plots[0]->Min(); + $n = count($this->plots); + for($i=0; $i < $n; ++$i) { + list($xm,$ym) = $this->plots[$i]->Min(); + $xmin = max($xmin,$xm); + $ymin = min($ymin,$ym); + } + return array($xmin,$ymin); + } + + function Max() { + list($xmax,$ymax) = $this->plots[0]->Max(); + $n = count($this->plots); + for($i=0; $i < $n; ++$i) { + list($xm,$ym) = $this->plots[$i]->Max(); + $xmax = max($xmax,$xm); + $ymax = max($ymax,$ym); + } + return array($xmax,$ymax); + } + + function GetCSIMareas() { + $n = count($this->plots); + $csimareas=''; + for($i=0; $i < $n; ++$i) { + $csimareas .= $this->plots[$i]->csimareas; + } + return $csimareas; + } + + // Stroke all the bars next to each other + function Stroke($img,$xscale,$yscale) { + $tmp=$xscale->off; + $n = count($this->plots); + $subwidth = $this->width/$this->nbrplots ; + + for( $i=0; $i < $n; ++$i ) { + $this->plots[$i]->ymin=$this->ybase; + $this->plots[$i]->SetWidth($subwidth); + + // If the client have used SetTextTickInterval() then + // major_step will be > 1 and the positioning will fail. + // If we assume it is always one the positioning will work + // fine with a text scale but this will not work with + // arbitrary linear scale + $xscale->off = $tmp+$i*round($xscale->scale_factor* $subwidth); + $this->plots[$i]->Stroke($img,$xscale,$yscale); + } + $xscale->off=$tmp; + } +} // Class + +//=================================================== +// CLASS AccBarPlot +// Description: Produce accumulated bar plots +//=================================================== +class AccBarPlot extends BarPlot { + private $plots=null,$nbrplots=0; +//--------------- +// CONSTRUCTOR + function AccBarPlot($plots) { + $this->plots = $plots; + $this->nbrplots = count($plots); + if( $this->nbrplots < 1 ) { + JpGraphError::Raise('Cannot create AccBarPlot from empty plot array.'); + } + for($i=0; $i < $this->nbrplots; ++$i ) { + if( empty($this->plots[$i]) || !isset($this->plots[$i]) ) { + JpGraphError::Raise("Acc bar plot element nbr $i is undefined or empty."); + } + } + $this->numpoints = $plots[0]->numpoints; + $this->value = new DisplayValue(); + } + +//--------------- +// PUBLIC METHODS + function Legend($graph) { + $n = count($this->plots); + for( $i=$n-1; $i >= 0; --$i ) { + $c = get_class($this->plots[$i]); + if( !($this->plots[$i] instanceof BarPlot) ) { + JpGraphError::Raise('One of the objects submitted to AccBar is not a BarPlot. Make sure that you create the AccBar plot from an array of BarPlot objects.(Class='.$c.')'); + } + $this->plots[$i]->DoLegend($graph); + } + } + + function Max() { + list($xmax) = $this->plots[0]->Max(); + $nmax=0; + for($i=0; $i < count($this->plots); ++$i) { + $n = count($this->plots[$i]->coords[0]); + $nmax = max($nmax,$n); + list($x) = $this->plots[$i]->Max(); + $xmax = max($xmax,$x); + } + for( $i = 0; $i < $nmax; $i++ ) { + // Get y-value for bar $i by adding the + // individual bars from all the plots added. + // It would be wrong to just add the + // individual plots max y-value since that + // would in most cases give to large y-value. + $y=0; + if( !isset($this->plots[0]->coords[0][$i]) ) { + JpGraphError::RaiseL(2014); + } + if( $this->plots[0]->coords[0][$i] > 0 ) + $y=$this->plots[0]->coords[0][$i]; + for( $j = 1; $j < $this->nbrplots; $j++ ) { + if( !isset($this->plots[$j]->coords[0][$i]) ) { + JpGraphError::RaiseL(2014); + } + if( $this->plots[$j]->coords[0][$i] > 0 ) + $y += $this->plots[$j]->coords[0][$i]; + } + $ymax[$i] = $y; + } + $ymax = max($ymax); + + // Bar always start at baseline + if( $ymax <= $this->ybase ) + $ymax = $this->ybase; + return array($xmax,$ymax); + } + + function Min() { + $nmax=0; + list($xmin,$ysetmin) = $this->plots[0]->Min(); + for($i=0; $i < count($this->plots); ++$i) { + $n = count($this->plots[$i]->coords[0]); + $nmax = max($nmax,$n); + list($x,$y) = $this->plots[$i]->Min(); + $xmin = Min($xmin,$x); + $ysetmin = Min($y,$ysetmin); + } + for( $i = 0; $i < $nmax; $i++ ) { + // Get y-value for bar $i by adding the + // individual bars from all the plots added. + // It would be wrong to just add the + // individual plots max y-value since that + // would in most cases give to large y-value. + $y=0; + if( $this->plots[0]->coords[0][$i] < 0 ) + $y=$this->plots[0]->coords[0][$i]; + for( $j = 1; $j < $this->nbrplots; $j++ ) { + if( $this->plots[$j]->coords[0][$i] < 0 ) + $y += $this->plots[ $j ]->coords[0][$i]; + } + $ymin[$i] = $y; + } + $ymin = Min($ysetmin,Min($ymin)); + // Bar always start at baseline + if( $ymin >= $this->ybase ) + $ymin = $this->ybase; + return array($xmin,$ymin); + } + + // Stroke acc bar plot + function Stroke($img,$xscale,$yscale) { + $pattern=NULL; + $img->SetLineWeight($this->weight); + for($i=0; $i < $this->numpoints-1; $i++) { + $accy = 0; + $accy_neg = 0; + for($j=0; $j < $this->nbrplots; ++$j ) { + $img->SetColor($this->plots[$j]->color); + + if ( $this->plots[$j]->coords[0][$i] >= 0) { + $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy); + $accyt=$yscale->Translate($accy); + $accy+=$this->plots[$j]->coords[0][$i]; + } + else { + //if ( $this->plots[$j]->coords[0][$i] < 0 || $accy_neg < 0 ) { + $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy_neg); + $accyt=$yscale->Translate($accy_neg); + $accy_neg+=$this->plots[$j]->coords[0][$i]; + } + + $xt=$xscale->Translate($i); + + if( $this->abswidth > -1 ) + $abswidth=$this->abswidth; + else + $abswidth=round($this->width*$xscale->scale_factor,0); + + $pts=array($xt,$accyt,$xt,$yt,$xt+$abswidth,$yt,$xt+$abswidth,$accyt); + + if( $this->bar_shadow ) { + $ssh = $this->bar_shadow_hsize; + $ssv = $this->bar_shadow_vsize; + + // We must also differ if we are a positive or negative bar. + if( $j === 0 ) { + // This gets extra complicated since we have to + // see all plots to see if we are negative. It could + // for example be that all plots are 0 until the very + // last one. We therefore need to save the initial setup + // for both the negative and positive case + + // In case the final bar is positive + $sp[0]=$pts[6]+1; $sp[1]=$pts[7]; + $sp[2]=$pts[6]+$ssh; $sp[3]=$pts[7]-$ssv; + + // In case the final bar is negative + $nsp[0]=$pts[0]; $nsp[1]=$pts[1]; + $nsp[2]=$pts[0]+$ssh; $nsp[3]=$pts[1]-$ssv; + $nsp[4]=$pts[6]+$ssh; $nsp[5]=$pts[7]-$ssv; + $nsp[10]=$pts[6]+1; $nsp[11]=$pts[7]; + } + + if( $j === $this->nbrplots-1 ) { + // If this is the last plot of the bar and + // the total value is larger than 0 then we + // add the shadow. + if( is_array($this->bar_shadow_color) ) { + $numcolors = count($this->bar_shadow_color); + if( $numcolors == 0 ) { + JpGraphError::Raise('You have specified an empty array for shadow colors in the bar plot.'); + } + $img->PushColor($this->bar_shadow_color[$i % $numcolors]); + } + else { + $img->PushColor($this->bar_shadow_color); + } + + if( $accy > 0 ) { + $sp[4]=$pts[4]+$ssh; $sp[5]=$pts[5]-$ssv; + $sp[6]=$pts[2]+$ssh; $sp[7]=$pts[3]-$ssv; + $sp[8]=$pts[2]; $sp[9]=$pts[3]-1; + $sp[10]=$pts[4]+1; $sp[11]=$pts[5]; + $img->FilledPolygon($sp,4); + } + elseif( $accy_neg < 0 ) { + $nsp[6]=$pts[4]+$ssh; $nsp[7]=$pts[5]-$ssv; + $nsp[8]=$pts[4]+1; $nsp[9]=$pts[5]; + $img->FilledPolygon($nsp,4); + } + $img->PopColor(); + } + } + + + // If value is NULL or 0, then don't draw a bar at all + if ($this->plots[$j]->coords[0][$i] == 0 ) continue; + + if( $this->plots[$j]->grad ) { + $grad = new Gradient($img); + $grad->FilledRectangle( + $pts[2],$pts[3], + $pts[6],$pts[7], + $this->plots[$j]->grad_fromcolor, + $this->plots[$j]->grad_tocolor, + $this->plots[$j]->grad_style); + } else { + if (is_array($this->plots[$j]->fill_color) ) { + $numcolors = count($this->plots[$j]->fill_color); + $fillcolor = $this->plots[$j]->fill_color[$i % $numcolors]; + // If the bar is specified to be non filled then the fill color is false + if( $fillcolor !== false ) + $img->SetColor($this->plots[$j]->fill_color[$i % $numcolors]); + } + else { + $fillcolor = $this->plots[$j]->fill_color; + if( $fillcolor !== false ) + $img->SetColor($this->plots[$j]->fill_color); + } + if( $fillcolor !== false ) + $img->FilledPolygon($pts); + $img->SetColor($this->plots[$j]->color); + } + + // Stroke the pattern + if( $this->plots[$j]->iPattern > -1 ) { + if( $pattern===NULL ) + $pattern = new RectPatternFactory(); + + $prect = $pattern->Create($this->plots[$j]->iPattern,$this->plots[$j]->iPatternColor,1); + $prect->SetDensity($this->plots[$j]->iPatternDensity); + if( $this->plots[$j]->coords[0][$i] < 0 ) { + $rx = $pts[0]; + $ry = $pts[1]; + } + else { + $rx = $pts[2]; + $ry = $pts[3]; + } + $width = abs($pts[4]-$pts[0])+1; + $height = abs($pts[1]-$pts[3])+1; + $prect->SetPos(new Rectangle($rx,$ry,$width,$height)); + $prect->Stroke($img); + } + + + // CSIM array + + if( $i < count($this->plots[$j]->csimtargets) ) { + // Create the client side image map + $rpts = $img->ArrRotate($pts); + $csimcoord=round($rpts[0]).", ".round($rpts[1]); + for( $k=1; $k < 4; ++$k){ + $csimcoord .= ", ".round($rpts[2*$k]).", ".round($rpts[2*$k+1]); + } + if( ! empty($this->plots[$j]->csimtargets[$i]) ) { + $this->csimareas.= 'csimareas.= " href=\"".$this->plots[$j]->csimtargets[$i]."\" "; + + if( ! empty($this->plots[$j]->csimwintargets[$i]) ) { + $this->csimareas.= " target=\"".$this->plots[$j]->csimwintargets[$i]."\" "; + } + + $sval=''; + if( !empty($this->plots[$j]->csimalts[$i]) ) { + $sval=sprintf($this->plots[$j]->csimalts[$i],$this->plots[$j]->coords[0][$i]); + $this->csimareas .= " title=\"$sval\" "; + } + $this->csimareas .= " alt=\"$sval\" />\n"; + } + } + + $pts[] = $pts[0]; + $pts[] = $pts[1]; + $img->SetLineWeight($this->plots[$j]->line_weight); + $img->Polygon($pts); + $img->SetLineWeight(1); + } + + // Draw labels for each acc.bar + + $x=$pts[2]+($pts[4]-$pts[2])/2; + if($this->bar_shadow) $x += $ssh; + + // First stroke the accumulated value for the entire bar + // This value is always placed at the top/bottom of the bars + if( $accy_neg < 0 ) { + $y=$yscale->Translate($accy_neg); + $this->value->Stroke($img,$accy_neg,$x,$y); + } + else { + $y=$yscale->Translate($accy); + $this->value->Stroke($img,$accy,$x,$y); + } + + $accy = 0; + $accy_neg = 0; + for($j=0; $j < $this->nbrplots; ++$j ) { + + // We don't print 0 values in an accumulated bar plot + if( $this->plots[$j]->coords[0][$i] == 0 ) continue; + + if ($this->plots[$j]->coords[0][$i] > 0) { + $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy); + $accyt=$yscale->Translate($accy); + if( $this->plots[$j]->valuepos=='center' ) { + $y = $accyt-($accyt-$yt)/2; + } + elseif( $this->plots[$j]->valuepos=='bottom' ) { + $y = $accyt; + } + else { // top or max + $y = $accyt-($accyt-$yt); + } + $accy+=$this->plots[$j]->coords[0][$i]; + if( $this->plots[$j]->valuepos=='center' ) { + $this->plots[$j]->value->SetAlign("center","center"); + $this->plots[$j]->value->SetMargin(0); + } + elseif( $this->plots[$j]->valuepos=='bottom' ) { + $this->plots[$j]->value->SetAlign('center','bottom'); + $this->plots[$j]->value->SetMargin(2); + } + else { + $this->plots[$j]->value->SetAlign('center','top'); + $this->plots[$j]->value->SetMargin(1); + } + } else { + $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy_neg); + $accyt=$yscale->Translate($accy_neg); + $accy_neg+=$this->plots[$j]->coords[0][$i]; + if( $this->plots[$j]->valuepos=='center' ) { + $y = $accyt-($accyt-$yt)/2; + } + elseif( $this->plots[$j]->valuepos=='bottom' ) { + $y = $accyt; + } + else { + $y = $accyt-($accyt-$yt); + } + if( $this->plots[$j]->valuepos=='center' ) { + $this->plots[$j]->value->SetAlign("center","center"); + $this->plots[$j]->value->SetMargin(0); + } + elseif( $this->plots[$j]->valuepos=='bottom' ) { + $this->plots[$j]->value->SetAlign('center',$j==0 ? 'bottom':'top'); + $this->plots[$j]->value->SetMargin(-2); + } + else { + $this->plots[$j]->value->SetAlign('center','bottom'); + $this->plots[$j]->value->SetMargin(-1); + } + } + $this->plots[$j]->value->Stroke($img,$this->plots[$j]->coords[0][$i],$x,$y); + } + + } + return true; + } +} // Class + +/* EOF */ +?> diff --git a/src/classes/jpgraph/jpgraph_errhandler.inc.php b/src/classes/jpgraph/jpgraph_errhandler.inc.php new file mode 100644 index 0000000..3c00cb4 --- /dev/null +++ b/src/classes/jpgraph/jpgraph_errhandler.inc.php @@ -0,0 +1,278 @@ +lt = $_jpg_messages; + } + + function Get($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) { + GLOBAL $__jpg_err_locale; + if( !isset($this->lt[$errnbr]) ) { + return 'Internal error: The specified error message ('.$errnbr.') does not exist in the chosen locale ('.$__jpg_err_locale.')'; + } + $ea = $this->lt[$errnbr]; + $j=0; + if( $a1 !== null ) { + $argv[$j++] = $a1; + if( $a2 !== null ) { + $argv[$j++] = $a2; + if( $a3 !== null ) { + $argv[$j++] = $a3; + if( $a4 !== null ) { + $argv[$j++] = $a4; + if( $a5 !== null ) { + $argv[$j++] = $a5; + } + } + } + } + } + $numargs = $j; + if( $ea[1] != $numargs ) { + // Error message argument count do not match. + // Just return the error message without arguments. + return $ea[0]; + } + switch( $numargs ) { + case 1: + $msg = sprintf($ea[0],$argv[0]); + break; + case 2: + $msg = sprintf($ea[0],$argv[0],$argv[1]); + break; + case 3: + $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2]); + break; + case 4: + $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3]); + break; + case 5: + $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3],$argv[4]); + break; + case 0: + default: + $msg = sprintf($ea[0]); + break; + } + return $msg; + } +} + +// +// A wrapper class that is used to access the specified error object +// (to hide the global error parameter and avoid having a GLOBAL directive +// in all methods. +// +class JpGraphError { + private static $__jpg_err; + public static function Install($aErrObject) { + self::$__jpg_err = new $aErrObject; + } + public static function Raise($aMsg,$aHalt=true){ + self::$__jpg_err->Raise($aMsg,$aHalt); + } + public static function SetErrLocale($aLoc) { + GLOBAL $__jpg_err_locale ; + $__jpg_err_locale = $aLoc; + } + public static function RaiseL($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) { + $t = new ErrMsgText(); + $msg = $t->Get($errnbr,$a1,$a2,$a3,$a4,$a5); + self::$__jpg_err->Raise($msg); + } +} + +// +// First of all set up a default error handler +// + +//============================================================= +// The default trivial text error handler. +//============================================================= +class JpGraphErrObject { + + protected $iTitle = "JpGraph Error"; + protected $iDest = false; + + + function JpGraphErrObject() { + // Empty. Reserved for future use + } + + function SetTitle($aTitle) { + $this->iTitle = $aTitle; + } + + function SetStrokeDest($aDest) { + $this->iDest = $aDest; + } + + // If aHalt is true then execution can't continue. Typical used for fatal errors + function Raise($aMsg,$aHalt=true) { + $aMsg = $this->iTitle.' '.$aMsg; + if ($this->iDest) { + $f = @fopen($this->iDest,'a'); + if( $f ) { + @fwrite($f,$aMsg); + @fclose($f); + } + } + else { + echo $aMsg; + } + if( $aHalt ) + die(); + } +} + +//============================================================== +// An image based error handler +//============================================================== +class JpGraphErrObjectImg extends JpGraphErrObject { + + function Raise($aMsg,$aHalt=true) { + $img_iconerror = + 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'. + 'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'. + 'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'. + 'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'. + 'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'. + 'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'. + '2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'. + 'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'. + 'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'. + 'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'. + '6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'. + 'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'. + 'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'. + 'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'. + 'qL72fwAAAABJRU5ErkJggg==' ; + + if( function_exists("imagetypes") ) + $supported = imagetypes(); + else + $supported = 0; + + if( !function_exists('imagecreatefromstring') ) + $supported = 0; + + if( ob_get_length() || headers_sent() || !($supported & IMG_PNG) ) { + // Special case for headers already sent or that the installation doesn't support + // the PNG format (which the error icon is encoded in). + // Dont return an image since it can't be displayed + die($this->iTitle.' '.$aMsg); + } + + $aMsg = wordwrap($aMsg,55); + $lines = substr_count($aMsg,"\n"); + + // Create the error icon GD + $erricon = Image::CreateFromString(base64_decode($img_iconerror)); + + // Create an image that contains the error text. + $w=400; + $h=100 + 15*max(0,$lines-3); + + $img = new Image($w,$h); + + + // Drop shadow + $img->SetColor("gray"); + $img->FilledRectangle(5,5,$w-1,$h-1,10); + $img->SetColor("gray:0.7"); + $img->FilledRectangle(5,5,$w-3,$h-3,10); + + // Window background + $img->SetColor("lightblue"); + $img->FilledRectangle(1,1,$w-5,$h-5); + $img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40); + + // Window border + $img->SetColor("black"); + $img->Rectangle(1,1,$w-5,$h-5); + $img->Rectangle(0,0,$w-4,$h-4); + + // Window top row + $img->SetColor("darkred"); + for($y=3; $y < 18; $y += 2 ) + $img->Line(1,$y,$w-6,$y); + + // "White shadow" + $img->SetColor("white"); + + // Left window edge + $img->Line(2,2,2,$h-5); + $img->Line(2,2,$w-6,2); + + // "Gray button shadow" + $img->SetColor("darkgray"); + + // Gray window shadow + $img->Line(2,$h-6,$w-5,$h-6); + $img->Line(3,$h-7,$w-5,$h-7); + + // Window title + $m = floor($w/2-5); + $l = 100; + $img->SetColor("lightgray:1.3"); + $img->FilledRectangle($m-$l,2,$m+$l,16); + + // Stroke text + $img->SetColor("darkred"); + $img->SetFont(FF_FONT2,FS_BOLD); + $img->StrokeText($m-50,15,$this->iTitle); + $img->SetColor("black"); + $img->SetFont(FF_FONT1,FS_NORMAL); + $txt = new Text($aMsg,52,25); + $txt->Align("left","top"); + $txt->Stroke($img); + if ($this->iDest) { + $img->Stream($this->iDest); + } else { + $img->Headers(); + $img->Stream(); + } + if( $aHalt ) + die(); + } +} + + +// Install the default error handler +if( USE_IMAGE_ERROR_HANDLER ) { + JpGraphError::Install("JpGraphErrObjectImg"); +} +else { + JpGraphError::Install("JpGraphErrObject"); +} + + +?> diff --git a/src/classes/jpgraph/jpgraph_gradient.php b/src/classes/jpgraph/jpgraph_gradient.php new file mode 100644 index 0000000..40a0d42 --- /dev/null +++ b/src/classes/jpgraph/jpgraph_gradient.php @@ -0,0 +1,423 @@ +img = $img; + } + + + function SetNumColors($aNum) { + $this->numcolors=$aNum; + } +//--------------- +// PUBLIC METHODS + // Produce a gradient filled rectangle with a smooth transition between + // two colors. + // ($xl,$yt) Top left corner + // ($xr,$yb) Bottom right + // $from_color Starting color in gradient + // $to_color End color in the gradient + // $style Which way is the gradient oriented? + function FilledRectangle($xl,$yt,$xr,$yb,$from_color,$to_color,$style=1) { + switch( $style ) { + case GRAD_VER: + $steps = ceil(abs($xr-$xl)); + $delta = $xr>=$xl ? 1 : -1; + $this->GetColArray($from_color,$to_color,$steps,$colors,$this->numcolors); + for( $i=0, $x=$xl; $i < $steps; ++$i ) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yt,$x,$yb); + $x += $delta; + } + break; + + case GRAD_HOR: + $steps = ceil(abs($yb-$yt)); + $delta = $yb>=$yt ? 1 : -1; + $this->GetColArray($from_color,$to_color,$steps,$colors,$this->numcolors); + for($i=0,$y=$yt; $i < $steps; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($xl,$y,$xr,$y); + $y += $delta; + } + break; + + case GRAD_MIDHOR: + $steps = ceil(abs($yb-$yt)/2); + $delta = $yb >= $yt ? 1 : -1; + $this->GetColArray($from_color,$to_color,$steps,$colors,$this->numcolors); + for($y=$yt, $i=0; $i < $steps; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($xl,$y,$xr,$y); + $y += $delta; + } + --$i; + if( abs($yb-$yt) % 2 == 1 ) --$steps; + for($j=0; $j < $steps; ++$j, --$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($xl,$y,$xr,$y); + $y += $delta; + } + $this->img->Line($xl,$y,$xr,$y); + break; + + case GRAD_MIDVER: + $steps = ceil(abs($xr-$xl)/2); + $delta = $xr>=$xl ? 1 : -1; + $this->GetColArray($from_color,$to_color,$steps,$colors,$this->numcolors); + for($x=$xl, $i=0; $i < $steps; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + --$i; + if( abs($xr-$xl) % 2 == 1 ) --$steps; + for($j=0; $j < $steps; ++$j, --$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + $this->img->Line($x,$yb,$x,$yt); + break; + + case GRAD_WIDE_MIDVER: + $diff = ceil(abs($xr-$xl)); + $steps = floor(abs($diff)/3); + $firststep = $diff - 2*$steps ; + $delta = $xr >= $xl ? 1 : -1; + $this->GetColArray($from_color,$to_color,$firststep,$colors,$this->numcolors); + for($x=$xl, $i=0; $i < $firststep; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + --$i; + $this->img->current_color = $colors[$i]; + for($j=0; $j< $steps; ++$j) { + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + + for($j=0; $j < $steps; ++$j, --$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + break; + + case GRAD_WIDE_MIDHOR: + $diff = ceil(abs($yb-$yt)); + $steps = floor(abs($diff)/3); + $firststep = $diff - 2*$steps ; + $delta = $yb >= $yt? 1 : -1; + $this->GetColArray($from_color,$to_color,$firststep,$colors,$this->numcolors); + for($y=$yt, $i=0; $i < $firststep; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($xl,$y,$xr,$y); + $y += $delta; + } + --$i; + $this->img->current_color = $colors[$i]; + for($j=0; $j < $steps; ++$j) { + $this->img->Line($xl,$y,$xr,$y); + $y += $delta; + } + for($j=0; $j < $steps; ++$j, --$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($xl,$y,$xr,$y); + $y += $delta; + } + break; + + case GRAD_LEFT_REFLECTION: + $steps1 = ceil(0.3*abs($xr-$xl)); + $delta = $xr>=$xl ? 1 : -1; + + $from_color = $this->img->rgb->Color($from_color); + $adj = 1.4; + $m = ($adj-1.0)*(255-min(255,min($from_color[0],min($from_color[1],$from_color[2])))); + $from_color2 = array(min(255,$from_color[0]+$m), + min(255,$from_color[1]+$m), min(255,$from_color[2]+$m)); + + $this->GetColArray($from_color2,$to_color,$steps1,$colors,$this->numcolors); + $n = count($colors); + for($x=$xl, $i=0; $i < $steps1 && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + $steps2 = max(1,ceil(0.08*abs($xr-$xl))); + $this->img->SetColor($to_color); + for($j=0; $j< $steps2; ++$j) { + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + $steps = abs($xr-$xl)-$steps1-$steps2; + $this->GetColArray($to_color,$from_color,$steps,$colors,$this->numcolors); + $n = count($colors); + for($i=0; $i < $steps && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + break; + + case GRAD_RIGHT_REFLECTION: + $steps1 = ceil(0.7*abs($xr-$xl)); + $delta = $xr>=$xl ? 1 : -1; + + $this->GetColArray($from_color,$to_color,$steps1,$colors,$this->numcolors); + $n = count($colors); + for($x=$xl, $i=0; $i < $steps1 && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + $steps2 = max(1,ceil(0.08*abs($xr-$xl))); + $this->img->SetColor($to_color); + for($j=0; $j< $steps2; ++$j) { + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + + $from_color = $this->img->rgb->Color($from_color); + $adj = 1.4; + $m = ($adj-1.0)*(255-min(255,min($from_color[0],min($from_color[1],$from_color[2])))); + $from_color = array(min(255,$from_color[0]+$m), + min(255,$from_color[1]+$m), min(255,$from_color[2]+$m)); + + $steps = abs($xr-$xl)-$steps1-$steps2; + $this->GetColArray($to_color,$from_color,$steps,$colors,$this->numcolors); + $n = count($colors); + for($i=0; $i < $steps && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + break; + + case GRAD_CENTER: + $steps = ceil(min(($yb-$yt)+1,($xr-$xl)+1)/2); + $this->GetColArray($from_color,$to_color,$steps,$colors,$this->numcolors); + $dx = ($xr-$xl)/2; + $dy = ($yb-$yt)/2; + $x=$xl;$y=$yt;$x2=$xr;$y2=$yb; + $n = count($colors); + for($x=$xl, $i=0; $x < $xl+$dx && $y < $yt+$dy && $i < $n; ++$x, ++$y, --$x2, --$y2, ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Rectangle($x,$y,$x2,$y2); + } + $this->img->Line($x,$y,$x2,$y2); + break; + + case GRAD_RAISED_PANEL: + // right to left + $steps1 = $xr-$xl; + $delta = $xr>=$xl ? 1 : -1; + $this->GetColArray($to_color,$from_color,$steps1,$colors,$this->numcolors); + $n = count($colors); + for($x=$xl, $i=0; $i < $steps1 && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + + // left to right + $xr -= 3; + $xl += 3; + $yb -= 3; + $yt += 3; + $steps2 = $xr-$xl; + $delta = $xr>=$xl ? 1 : -1; + for($x=$xl, $j=$steps2; $j >= 0; --$j) { + $this->img->current_color = $colors[$j]; + $this->img->Line($x,$yb,$x,$yt); + $x += $delta; + } + break; + + case GRAD_DIAGONAL: + // use the longer dimension to determine the required number of steps. + // first loop draws from one corner to the mid-diagonal and the second + // loop draws from the mid-diagonal to the opposing corner. + if($xr-$xl > $yb - $yt) { + // width is greater than height -> use x-dimension for steps + $steps = $xr-$xl; + $delta = $xr>=$xl ? 1 : -1; + $this->GetColArray($from_color,$to_color,$steps*2,$colors,$this->numcolors); + $n = count($colors); + + for($x=$xl, $i=0; $i < $steps && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $y = $yt+($i/$steps)*($yb-$yt)*$delta; + $this->img->Line($x,$yt,$xl,$y); + $x += $delta; + } + + for($x=$xl, $i = 0; $i < $steps && $i < $n; ++$i) { + $this->img->current_color = $colors[$steps+$i]; + $y = $yt+($i/$steps)*($yb-$yt)*$delta; + $this->img->Line($x,$yb,$xr,$y); + $x += $delta; + } + } else { + // height is greater than width -> use y-dimension for steps + $steps = $yb-$yt; + $delta = $yb>=$yt ? 1 : -1; + $this->GetColArray($from_color,$to_color,$steps*2,$colors,$this->numcolors); + $n = count($colors); + + for($y=$yt, $i=0; $i < $steps && $i < $n; ++$i) { + $this->img->current_color = $colors[$i]; + $x = $xl+($i/$steps)*($xr-$xl)*$delta; + $this->img->Line($x,$yt,$xl,$y); + $y += $delta; + } + + for($y=$yt, $i = 0; $i < $steps && $i < $n; ++$i) { + $this->img->current_color = $colors[$steps+$i]; + $x = $xl+($i/$steps)*($xr-$xl)*$delta; + $this->img->Line($x,$yb,$xr,$y); + $x += $delta; + } + + } + break; + + default: + JpGraphError::RaiseL(7001,$style); +//("Unknown gradient style (=$style)."); + break; + } + } + + // Fill a special case of a polygon with a flat bottom + // with a gradient. Can be used for filled line plots. + // Please note that this is NOT a generic gradient polygon fill + // routine. It assumes that the bottom is flat (like a drawing + // of a mountain) + function FilledFlatPolygon($pts,$from_color,$to_color) { + if( count($pts) == 0 ) return; + + $maxy=$pts[1]; + $miny=$pts[1]; + $n = count($pts) ; + for( $i=0, $idx=0; $i < $n; $i += 2) { + $x = round($pts[$i]); + $y = round($pts[$i+1]); + $miny = min($miny,$y); + $maxy = max($maxy,$y); + } + + $colors = array(); + $this->GetColArray($from_color,$to_color,abs($maxy-$miny)+1,$colors,$this->numcolors); + for($i=$miny, $idx=0; $i <= $maxy; ++$i ) { + $colmap[$i] = $colors[$idx++]; + } + + $n = count($pts)/2 ; + $idx = 0 ; + while( $idx < $n-1 ) { + $p1 = array(round($pts[$idx*2]),round($pts[$idx*2+1])); + $p2 = array(round($pts[++$idx*2]),round($pts[$idx*2+1])); + + // Find the largest rectangle we can fill + $y = max($p1[1],$p2[1]) ; + for($yy=$maxy; $yy > $y; --$yy) { + $this->img->current_color = $colmap[$yy]; + $this->img->Line($p1[0],$yy,$p2[0]-1,$yy); + } + + if( $p1[1] == $p2[1] ) continue; + + // Fill the rest using lines (slow...) + $slope = ($p2[0]-$p1[0])/($p1[1]-$p2[1]); + $x1 = $p1[0]; + $x2 = $p2[0]-1; + $start = $y; + if( $p1[1] > $p2[1] ) { + while( $y >= $p2[1] ) { + $x1=$slope*($start-$y)+$p1[0]; + $this->img->current_color = $colmap[$y]; + $this->img->Line($x1,$y,$x2,$y); + --$y; + } + } + else { + while( $y >= $p1[1] ) { + $x2=$p2[0]+$slope*($start-$y); + $this->img->current_color = $colmap[$y]; + $this->img->Line($x1,$y,$x2,$y); + --$y; + } + } + } + } + +//--------------- +// PRIVATE METHODS + // Add to the image color map the necessary colors to do the transition + // between the two colors using $numcolors intermediate colors + function GetColArray($from_color,$to_color,$arr_size,&$colors,$numcols=100) { + if( $arr_size==0 ) return; + // If color is given as text get it's corresponding r,g,b values + $from_color = $this->img->rgb->Color($from_color); + $to_color = $this->img->rgb->Color($to_color); + + $rdelta=($to_color[0]-$from_color[0])/$numcols; + $gdelta=($to_color[1]-$from_color[1])/$numcols; + $bdelta=($to_color[2]-$from_color[2])/$numcols; + $colorsperstep = $numcols/$arr_size; + $prevcolnum = -1; + $from_alpha = $from_color[3]; + $to_alpha = $to_color[3]; + $adelta = ( $to_alpha - $from_alpha ) / $numcols ; + for ($i=0; $i < $arr_size; ++$i) { + $colnum = floor($colorsperstep*$i); + if ( $colnum == $prevcolnum ) + $colors[$i] = $colidx; + else { + $r = floor($from_color[0] + $colnum*$rdelta); + $g = floor($from_color[1] + $colnum*$gdelta); + $b = floor($from_color[2] + $colnum*$bdelta); + $alpha = $from_alpha + $colnum*$adelta; + $colidx = $this->img->rgb->Allocate(sprintf("#%02x%02x%02x",$r,$g,$b),$alpha); + $colors[$i] = $colidx; + } + $prevcolnum = $colnum; + } + } +} // Class + +?> diff --git a/src/classes/jpgraph/jpgraph_line.php b/src/classes/jpgraph/jpgraph_line.php new file mode 100644 index 0000000..26eb622 --- /dev/null +++ b/src/classes/jpgraph/jpgraph_line.php @@ -0,0 +1,632 @@ +Plot($datay,$datax); + $this->mark = new PlotMark() ; + } +//--------------- +// PUBLIC METHODS + + // Set style, filled or open + function SetFilled($aFlag=true) { + JpGraphError::RaiseL(10001);//('LinePlot::SetFilled() is deprecated. Use SetFillColor()'); + } + + function SetBarCenter($aFlag=true) { + $this->barcenter=$aFlag; + } + + function SetStyle($aStyle) { + $this->line_style=$aStyle; + } + + function SetStepStyle($aFlag=true) { + $this->step_style = $aFlag; + } + + function SetColor($aColor) { + parent::SetColor($aColor); + } + + function SetFillFromYMin($f=true) { + $this->fillFromMin = $f ; + } + + function SetFillColor($aColor,$aFilled=true) { + $this->fill_color=$aColor; + $this->filled=$aFilled; + } + + function SetFillGradient($aFromColor,$aToColor,$aNumColors=100,$aFilled=true) { + $this->fillgrad_fromcolor = $aFromColor; + $this->fillgrad_tocolor = $aToColor; + $this->fillgrad_numcolors = $aNumColors; + $this->filled = $aFilled; + $this->fillgrad = true; + } + + function Legend($graph) { + if( $this->legend!="" ) { + if( $this->filled && !$this->fillgrad ) { + $graph->legend->Add($this->legend, + $this->fill_color,$this->mark,0, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + elseif( $this->fillgrad ) { + $color=array($this->fillgrad_fromcolor,$this->fillgrad_tocolor); + // In order to differentiate between gradients and cooors specified as an RGB triple + $graph->legend->Add($this->legend,$color,"",-2 /* -GRAD_HOR */, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } else { + $graph->legend->Add($this->legend, + $this->color,$this->mark,$this->line_style, + $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget); + } + } + } + + function AddArea($aMin=0,$aMax=0,$aFilled=LP_AREA_NOT_FILLED,$aColor="gray9",$aBorder=LP_AREA_BORDER) { + if($aMin > $aMax) { + // swap + $tmp = $aMin; + $aMin = $aMax; + $aMax = $tmp; + } + $this->filledAreas[] = array($aMin,$aMax,$aColor,$aFilled,$aBorder); + } + + // Gets called before any axis are stroked + function PreStrokeAdjust($graph) { + + // If another plot type have already adjusted the + // offset we don't touch it. + // (We check for empty in case the scale is a log scale + // and hence doesn't contain any xlabel_offset) + if( empty($graph->xaxis->scale->ticks->xlabel_offset) || + $graph->xaxis->scale->ticks->xlabel_offset == 0 ) { + if( $this->center ) { + ++$this->numpoints; + $a=0.5; $b=0.5; + } else { + $a=0; $b=0; + } + $graph->xaxis->scale->ticks->SetXLabelOffset($a); + $graph->SetTextScaleOff($b); + //$graph->xaxis->scale->ticks->SupressMinorTickMarks(); + } + } + + function SetFastStroke($aFlg=true) { + $this->iFastStroke = $aFlg; + } + + function FastStroke($img,$xscale,$yscale,$aStartPoint=0,$exist_x=true) { + // An optimized stroke for many data points with no extra + // features but 60% faster. You can't have values or line styles, or null + // values in plots. + $numpoints=count($this->coords[0]); + if( $this->barcenter ) + $textadj = 0.5-$xscale->text_scale_off; + else + $textadj = 0; + + $img->SetColor($this->color); + $img->SetLineWeight($this->weight); + $pnts=$aStartPoint; + while( $pnts < $numpoints ) { + if( $exist_x ) $x=$this->coords[1][$pnts]; + else $x=$pnts+$textadj; + $xt = $xscale->Translate($x); + $y=$this->coords[0][$pnts]; + $yt = $yscale->Translate($y); + if( is_numeric($y) ) { + $cord[] = $xt; + $cord[] = $yt; + } + elseif( $y == '-' && $pnts > 0 ) { + // Just ignore + } + else { + JpGraphError::RaiseL(10002);//('Plot too complicated for fast line Stroke. Use standard Stroke()'); + } + ++$pnts; + } // WHILE + + $img->Polygon($cord,false,true); + } + + function Stroke($img,$xscale,$yscale) { + $idx=0; + $numpoints=count($this->coords[0]); + if( isset($this->coords[1]) ) { + if( count($this->coords[1])!=$numpoints ) + JpGraphError::RaiseL(2003,count($this->coords[1]),$numpoints); +//("Number of X and Y points are not equal. Number of X-points:".count($this->coords[1])." Number of Y-points:$numpoints"); + else + $exist_x = true; + } + else + $exist_x = false; + + if( $this->barcenter ) + $textadj = 0.5-$xscale->text_scale_off; + else + $textadj = 0; + + // Find the first numeric data point + $startpoint=0; + while( $startpoint < $numpoints && !is_numeric($this->coords[0][$startpoint]) ) + ++$startpoint; + + // Bail out if no data points + if( $startpoint == $numpoints ) + return; + + if( $this->iFastStroke ) { + $this->FastStroke($img,$xscale,$yscale,$startpoint,$exist_x); + return; + } + + if( $exist_x ) + $xs=$this->coords[1][$startpoint]; + else + $xs= $textadj+$startpoint; + + $img->SetStartPoint($xscale->Translate($xs), + $yscale->Translate($this->coords[0][$startpoint])); + + + if( $this->filled ) { + $min = $yscale->GetMinVal(); + if( $min > 0 || $this->fillFromMin ) + $fillmin = $yscale->scale_abs[0];//Translate($min); + else + $fillmin = $yscale->Translate(0); + + $cord[$idx++] = $xscale->Translate($xs); + $cord[$idx++] = $fillmin; + } + $xt = $xscale->Translate($xs); + $yt = $yscale->Translate($this->coords[0][$startpoint]); + $cord[$idx++] = $xt; + $cord[$idx++] = $yt; + $yt_old = $yt; + $xt_old = $xt; + $y_old = $this->coords[0][$startpoint]; + + $this->value->Stroke($img,$this->coords[0][$startpoint],$xt,$yt); + + $img->SetColor($this->color); + $img->SetLineWeight($this->weight); + $img->SetLineStyle($this->line_style); + $pnts=$startpoint+1; + $firstnonumeric = false; + + + while( $pnts < $numpoints ) { + + if( $exist_x ) $x=$this->coords[1][$pnts]; + else $x=$pnts+$textadj; + $xt = $xscale->Translate($x); + $yt = $yscale->Translate($this->coords[0][$pnts]); + + $y=$this->coords[0][$pnts]; + if( $this->step_style ) { + // To handle null values within step style we need to record the + // first non numeric value so we know from where to start if the + // non value is '-'. + if( is_numeric($y) ) { + $firstnonumeric = false; + if( is_numeric($y_old) ) { + $img->StyleLine($xt_old,$yt_old,$xt,$yt_old); + $img->StyleLine($xt,$yt_old,$xt,$yt); + } + elseif( $y_old == '-' ) { + $img->StyleLine($xt_first,$yt_first,$xt,$yt_first); + $img->StyleLine($xt,$yt_first,$xt,$yt); + } + else { + $yt_old = $yt; + $xt_old = $xt; + } + $cord[$idx++] = $xt; + $cord[$idx++] = $yt_old; + $cord[$idx++] = $xt; + $cord[$idx++] = $yt; + } + elseif( $firstnonumeric==false ) { + $firstnonumeric = true; + $yt_first = $yt_old; + $xt_first = $xt_old; + } + } + else { + $tmp1=$y; + $prev=$this->coords[0][$pnts-1]; + if( $tmp1==='' || $tmp1===NULL || $tmp1==='X' ) $tmp1 = 'x'; + if( $prev==='' || $prev===null || $prev==='X' ) $prev = 'x'; + + if( is_numeric($y) || (is_string($y) && $y != '-') ) { + if( is_numeric($y) && (is_numeric($prev) || $prev === '-' ) ) { + $img->StyleLineTo($xt,$yt); + } + else { + $img->SetStartPoint($xt,$yt); + } + } + if( $this->filled && $tmp1 !== '-' ) { + if( $tmp1 === 'x' ) { + $cord[$idx++] = $cord[$idx-3]; + $cord[$idx++] = $fillmin; + } + elseif( $prev === 'x' ) { + $cord[$idx++] = $xt; + $cord[$idx++] = $fillmin; + $cord[$idx++] = $xt; + $cord[$idx++] = $yt; + } + else { + $cord[$idx++] = $xt; + $cord[$idx++] = $yt; + } + } + else { + if( is_numeric($tmp1) && (is_numeric($prev) || $prev === '-' ) ) { + $cord[$idx++] = $xt; + $cord[$idx++] = $yt; + } + } + } + $yt_old = $yt; + $xt_old = $xt; + $y_old = $y; + + $this->StrokeDataValue($img,$this->coords[0][$pnts],$xt,$yt); + + ++$pnts; + } + + if( $this->filled ) { + $cord[$idx++] = $xt; + if( $min > 0 || $this->fillFromMin ) + $cord[$idx++] = $yscale->Translate($min); + else + $cord[$idx++] = $yscale->Translate(0); + if( $this->fillgrad ) { + $img->SetLineWeight(1); + $grad = new Gradient($img); + $grad->SetNumColors($this->fillgrad_numcolors); + $grad->FilledFlatPolygon($cord,$this->fillgrad_fromcolor,$this->fillgrad_tocolor); + $img->SetLineWeight($this->weight); + } + else { + $img->SetColor($this->fill_color); + $img->FilledPolygon($cord); + } + if( $this->line_weight > 0 ) { + $img->SetColor($this->color); + $img->Polygon($cord); + } + } + + if(!empty($this->filledAreas)) { + + $minY = $yscale->Translate($yscale->GetMinVal()); + $factor = ($this->step_style ? 4 : 2); + + for($i = 0; $i < sizeof($this->filledAreas); ++$i) { + // go through all filled area elements ordered by insertion + // fill polygon array + $areaCoords[] = $cord[$this->filledAreas[$i][0] * $factor]; + $areaCoords[] = $minY; + + $areaCoords = + array_merge($areaCoords, + array_slice($cord, + $this->filledAreas[$i][0] * $factor, + ($this->filledAreas[$i][1] - $this->filledAreas[$i][0] + ($this->step_style ? 0 : 1)) * $factor)); + $areaCoords[] = $areaCoords[sizeof($areaCoords)-2]; // last x + $areaCoords[] = $minY; // last y + + if($this->filledAreas[$i][3]) { + $img->SetColor($this->filledAreas[$i][2]); + $img->FilledPolygon($areaCoords); + $img->SetColor($this->color); + } + // Check if we should draw the frame. + // If not we still re-draw the line since it might have been + // partially overwritten by the filled area and it doesn't look + // very good. + // TODO: The behaviour is undefined if the line does not have + // any line at the position of the area. + if( $this->filledAreas[$i][4] ) + $img->Polygon($areaCoords); + else + $img->Polygon($cord); + + $areaCoords = array(); + } + } + + if( $this->mark->type == -1 || $this->mark->show == false ) + return; + + for( $pnts=0; $pnts<$numpoints; ++$pnts) { + + if( $exist_x ) $x=$this->coords[1][$pnts]; + else $x=$pnts+$textadj; + $xt = $xscale->Translate($x); + $yt = $yscale->Translate($this->coords[0][$pnts]); + + if( is_numeric($this->coords[0][$pnts]) ) { + if( !empty($this->csimtargets[$pnts]) ) { + if( !empty($this->csimwintargets[$pnts]) ) { + $this->mark->SetCSIMTarget($this->csimtargets[$pnts],$this->csimwintargets[$pnts]); + } + else { + $this->mark->SetCSIMTarget($this->csimtargets[$pnts]); + } + $this->mark->SetCSIMAlt($this->csimalts[$pnts]); + } + if( $exist_x ) + $x=$this->coords[1][$pnts]; + else + $x=$pnts; + $this->mark->SetCSIMAltVal($this->coords[0][$pnts],$x); + $this->mark->Stroke($img,$xt,$yt); + $this->csimareas .= $this->mark->GetCSIMAreas(); + } + } + } +} // Class + + +//=================================================== +// CLASS AccLinePlot +// Description: +//=================================================== +class AccLinePlot extends Plot { + protected $plots=null,$nbrplots=0; + private $iStartEndZero=true; +//--------------- +// CONSTRUCTOR + function AccLinePlot($plots) { + $this->plots = $plots; + $this->nbrplots = count($plots); + $this->numpoints = $plots[0]->numpoints; + + // Verify that all plots have the same number of data points + for( $i=1; $i < $this->nbrplots; ++$i ) { + if( $plots[$i]->numpoints != $this->numpoints ) { + JpGraphError::RaiseL(10003);//('Each plot in an accumulated lineplot must have the same number of data points',0) + } + } + + for($i=0; $i < $this->nbrplots; ++$i ) { + $this->LineInterpolate($this->plots[$i]->coords[0]); + } + } + +//--------------- +// PUBLIC METHODS + function Legend($graph) { + foreach( $this->plots as $p ) + $p->DoLegend($graph); + } + + function Max() { + list($xmax) = $this->plots[0]->Max(); + $nmax=0; + $n = count($this->plots); + for($i=0; $i < $n; ++$i) { + $nc = count($this->plots[$i]->coords[0]); + $nmax = max($nmax,$nc); + list($x) = $this->plots[$i]->Max(); + $xmax = Max($xmax,$x); + } + for( $i = 0; $i < $nmax; $i++ ) { + // Get y-value for line $i by adding the + // individual bars from all the plots added. + // It would be wrong to just add the + // individual plots max y-value since that + // would in most cases give to large y-value. + $y=$this->plots[0]->coords[0][$i]; + for( $j = 1; $j < $this->nbrplots; $j++ ) { + $y += $this->plots[ $j ]->coords[0][$i]; + } + $ymax[$i] = $y; + } + $ymax = max($ymax); + return array($xmax,$ymax); + } + + function Min() { + $nmax=0; + list($xmin,$ysetmin) = $this->plots[0]->Min(); + $n = count($this->plots); + for($i=0; $i < $n; ++$i) { + $nc = count($this->plots[$i]->coords[0]); + $nmax = max($nmax,$nc); + list($x,$y) = $this->plots[$i]->Min(); + $xmin = Min($xmin,$x); + $ysetmin = Min($y,$ysetmin); + } + for( $i = 0; $i < $nmax; $i++ ) { + // Get y-value for line $i by adding the + // individual bars from all the plots added. + // It would be wrong to just add the + // individual plots min y-value since that + // would in most cases give to small y-value. + $y=$this->plots[0]->coords[0][$i]; + for( $j = 1; $j < $this->nbrplots; $j++ ) { + $y += $this->plots[ $j ]->coords[0][$i]; + } + $ymin[$i] = $y; + } + $ymin = Min($ysetmin,Min($ymin)); + return array($xmin,$ymin); + } + + // Gets called before any axis are stroked + function PreStrokeAdjust($graph) { + + // If another plot type have already adjusted the + // offset we don't touch it. + // (We check for empty in case the scale is a log scale + // and hence doesn't contain any xlabel_offset) + + if( empty($graph->xaxis->scale->ticks->xlabel_offset) || + $graph->xaxis->scale->ticks->xlabel_offset == 0 ) { + if( $this->center ) { + ++$this->numpoints; + $a=0.5; $b=0.5; + } else { + $a=0; $b=0; + } + $graph->xaxis->scale->ticks->SetXLabelOffset($a); + $graph->SetTextScaleOff($b); + $graph->xaxis->scale->ticks->SupressMinorTickMarks(); + } + + } + + function SetInterpolateMode($aIntMode) { + $this->iStartEndZero=$aIntMode; + } + + // Replace all '-' with an interpolated value. We use straightforward + // linear interpolation. If the data starts with one or several '-' they + // will be replaced by the the first valid data point + function LineInterpolate(&$aData) { + + $n=count($aData); + $i=0; + + // If first point is undefined we will set it to the same as the first + // valid data + if( $aData[$i]==='-' ) { + // Find the first valid data + while( $i < $n && $aData[$i]==='-' ) { + ++$i; + } + if( $i < $n ) { + for($j=0; $j < $i; ++$j ) { + if( $this->iStartEndZero ) + $aData[$i] = 0; + else + $aData[$j] = $aData[$i]; + } + } + else { + // All '-' => Error + return false; + } + } + + while($i < $n) { + while( $i < $n && $aData[$i] !== '-' ) { + ++$i; + } + if( $i < $n ) { + $pstart=$i-1; + + // Now see how long this segment of '-' are + while( $i < $n && $aData[$i] === '-' ) + ++$i; + if( $i < $n ) { + $pend=$i; + $size=$pend-$pstart; + $k=($aData[$pend]-$aData[$pstart])/$size; + // Replace the segment of '-' with a linear interpolated value. + for($j=1; $j < $size; ++$j ) { + $aData[$pstart+$j] = $aData[$pstart] + $j*$k ; + } + } + else { + // There are no valid end point. The '-' goes all the way to the end + // In that case we just set all the remaining values the the same as the + // last valid data point. + for( $j=$pstart+1; $j < $n; ++$j ) + if( $this->iStartEndZero ) + $aData[$j] = 0; + else + $aData[$j] = $aData[$pstart] ; + } + } + } + return true; + } + + + + // To avoid duplicate of line drawing code here we just + // change the y-values for each plot and then restore it + // after we have made the stroke. We must do this copy since + // it wouldn't be possible to create an acc line plot + // with the same graphs, i.e AccLinePlot(array($pl,$pl,$pl)); + // since this method would have a side effect. + function Stroke($img,$xscale,$yscale) { + $img->SetLineWeight($this->weight); + $this->numpoints = count($this->plots[0]->coords[0]); + // Allocate array + $coords[$this->nbrplots][$this->numpoints]=0; + for($i=0; $i<$this->numpoints; $i++) { + $coords[0][$i]=$this->plots[0]->coords[0][$i]; + $accy=$coords[0][$i]; + for($j=1; $j<$this->nbrplots; ++$j ) { + $coords[$j][$i] = $this->plots[$j]->coords[0][$i]+$accy; + $accy = $coords[$j][$i]; + } + } + for($j=$this->nbrplots-1; $j>=0; --$j) { + $p=$this->plots[$j]; + for( $i=0; $i<$this->numpoints; ++$i) { + $tmp[$i]=$p->coords[0][$i]; + $p->coords[0][$i]=$coords[$j][$i]; + } + $p->Stroke($img,$xscale,$yscale); + for( $i=0; $i<$this->numpoints; ++$i) + $p->coords[0][$i]=$tmp[$i]; + $p->coords[0][]=$tmp; + } + } +} // Class + + +/* EOF */ +?> diff --git a/src/classes/jpgraph/jpgraph_pie.php b/src/classes/jpgraph/jpgraph_pie.php new file mode 100644 index 0000000..0a9c57d --- /dev/null +++ b/src/classes/jpgraph/jpgraph_pie.php @@ -0,0 +1,1439 @@ + array(136,34,40,45,46,62,63,134,74,10,120,136,141,168,180,77,209,218,346,395,89,430), + "pastel" => array(27,415,128,59,66,79,105,110,42,147,152,230,236,240,331,337,405,38), + "water" => array(8,370,24,40,335,56,213,237,268,14,326,387,10,388), + "sand" => array(27,168,34,170,19,50,65,72,131,209,46,393)); + protected $theme="earth"; + protected $setslicecolors=array(); + protected $labeltype=0; // Default to percentage + protected $pie_border=true,$pie_interior_border=true; + public $value; + protected $ishadowcolor='',$ishadowdrop=4; + protected $ilabelposadj=1; + protected $legendcsimtargets = array(),$legendcsimwintargets = array(); + protected $legendcsimalts = array(); + protected $adjusted_data = array(); + public $guideline = null; + protected $guidelinemargin=10,$iShowGuideLineForSingle = false; + protected $iGuideLineCurve = false,$iGuideVFactor=1.4,$iGuideLineRFactor=0.8; + protected $la = array(); // Holds the exact angle for each label + +//--------------- +// CONSTRUCTOR + function PiePlot($data) { + $this->data = array_reverse($data); + $this->title = new Text(""); + $this->title->SetFont(FF_FONT1,FS_BOLD); + $this->value = new DisplayValue(); + $this->value->Show(); + $this->value->SetFormat('%.1f%%'); + $this->guideline = new LineProperty(); + } + +//--------------- +// PUBLIC METHODS + function SetCenter($x,$y=0.5) { + $this->posx = $x; + $this->posy = $y; + } + + // Enable guideline and set drwaing policy + function SetGuideLines($aFlg=true,$aCurved=true,$aAlways=false) { + $this->guideline->Show($aFlg); + $this->iShowGuideLineForSingle = $aAlways; + $this->iGuideLineCurve = $aCurved; + } + + // Adjuste the distance between labels and labels and pie + function SetGuideLinesAdjust($aVFactor,$aRFactor=0.8) { + $this->iGuideVFactor=$aVFactor; + $this->iGuideLineRFactor=$aRFactor; + } + + function SetColor($aColor) { + $this->color = $aColor; + } + + function SetSliceColors($aColors) { + $this->setslicecolors = $aColors; + } + + function SetShadow($aColor='darkgray',$aDropWidth=4) { + $this->ishadowcolor = $aColor; + $this->ishadowdrop = $aDropWidth; + } + + function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') { + $this->csimtargets=array_reverse($aTargets); + if( is_array($aWinTargets) ) + $this->csimwintargets=array_reverse($aWinTargets); + if( is_array($aAlts) ) + $this->csimalts=array_reverse($aAlts); + } + + function GetCSIMareas() { + return $this->csimareas; + } + + function AddSliceToCSIM($i,$xc,$yc,$radius,$sa,$ea) { + //Slice number, ellipse centre (x,y), height, width, start angle, end angle + while( $sa > 2*M_PI ) $sa = $sa - 2*M_PI; + while( $ea > 2*M_PI ) $ea = $ea - 2*M_PI; + + $sa = 2*M_PI - $sa; + $ea = 2*M_PI - $ea; + + // Special case when we have only one slice since then both start and end + // angle will be == 0 + if( abs($sa - $ea) < 0.0001 ) { + $sa=2*M_PI; $ea=0; + } + + //add coordinates of the centre to the map + $xc = floor($xc);$yc=floor($yc); + $coords = "$xc, $yc"; + + //add coordinates of the first point on the arc to the map + $xp = floor(($radius*cos($ea))+$xc); + $yp = floor($yc-$radius*sin($ea)); + $coords.= ", $xp, $yp"; + + //add coordinates every 0.2 radians + $a=$ea+0.2; + + // If we cross the 360-limit with a slice we need to handle + // the fact that end angle is smaller than start + if( $sa < $ea ) { + while ($a <= 2*M_PI) { + $xp = floor($radius*cos($a)+$xc); + $yp = floor($yc-$radius*sin($a)); + $coords.= ", $xp, $yp"; + $a += 0.2; + } + $a -= 2*M_PI; + } + + + while ($a < $sa) { + $xp = floor($radius*cos($a)+$xc); + $yp = floor($yc-$radius*sin($a)); + $coords.= ", $xp, $yp"; + $a += 0.2; + } + + //Add the last point on the arc + $xp = floor($radius*cos($sa)+$xc); + $yp = floor($yc-$radius*sin($sa)); + $coords.= ", $xp, $yp"; + if( !empty($this->csimtargets[$i]) ) { + $this->csimareas .= "csimtargets[$i]."\""; + $tmp=""; + if( !empty($this->csimwintargets[$i]) ) { + $this->csimareas .= " target=\"".$this->csimwintargets[$i]."\" "; + } + if( !empty($this->csimalts[$i]) ) { + $tmp=sprintf($this->csimalts[$i],$this->data[$i]); + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + } + + + function SetTheme($aTheme) { + if( in_array($aTheme,array_keys($this->themearr)) ) + $this->theme = $aTheme; + else + JpGraphError::RaiseL(15001,$aTheme);//("PiePLot::SetTheme() Unknown theme: $aTheme"); + } + + function ExplodeSlice($e,$radius=20) { + if( ! is_integer($e) ) + JpGraphError::RaiseL(15002);//('Argument to PiePlot::ExplodeSlice() must be an integer'); + $this->explode_radius[$e]=$radius; + } + + function ExplodeAll($radius=20) { + $this->explode_all=true; + $this->explode_r = $radius; + } + + function Explode($aExplodeArr) { + if( !is_array($aExplodeArr) ) { + JpGraphError::RaiseL(15003); +//("Argument to PiePlot::Explode() must be an array with integer distances."); + } + $this->explode_radius = $aExplodeArr; + } + + function SetStartAngle($aStart) { + if( $aStart < 0 || $aStart > 360 ) { + JpGraphError::RaiseL(15004);//('Slice start angle must be between 0 and 360 degrees.'); + } + $this->startangle = 360-$aStart; + $this->startangle *= M_PI/180; + } + + function SetFont($family,$style=FS_NORMAL,$size=10) { + JpGraphError::RaiseL(15005);//('PiePlot::SetFont() is deprecated. Use PiePlot->value->SetFont() instead.'); + } + + // Size in percentage + function SetSize($aSize) { + if( ($aSize>0 && $aSize<=0.5) || ($aSize>10 && $aSize<1000) ) + $this->radius = $aSize; + else + JpGraphError::RaiseL(15006); +//("PiePlot::SetSize() Radius for pie must either be specified as a fraction [0, 0.5] of the size of the image or as an absolute size in pixels in the range [10, 1000]"); + } + + function SetFontColor($aColor) { + JpGraphError::RaiseL(15007); +//('PiePlot::SetFontColor() is deprecated. Use PiePlot->value->SetColor() instead.'); + } + + // Set label arrays + function SetLegends($aLegend) { + $this->legends = $aLegend; + } + + // Set text labels for slices + function SetLabels($aLabels,$aLblPosAdj="auto") { + $this->labels = array_reverse($aLabels); + $this->ilabelposadj=$aLblPosAdj; + } + + function SetLabelPos($aLblPosAdj) { + $this->ilabelposadj=$aLblPosAdj; + } + + // Should we display actual value or percentage? + function SetLabelType($t) { + if( $t < 0 || $t > 2 ) + JpGraphError::RaiseL(15008,$t); +//("PiePlot::SetLabelType() Type for pie plots must be 0 or 1 (not $t)."); + $this->labeltype=$t; + } + + // Deprecated. + function SetValueType($aType) { + $this->SetLabelType($aType); + } + + // Should the circle around a pie plot be displayed + function ShowBorder($exterior=true,$interior=true) { + $this->pie_border = $exterior; + $this->pie_interior_border = $interior; + } + + // Setup the legends + function Legend($graph) { + $colors = array_keys($graph->img->rgb->rgb_table); + sort($colors); + $ta=$this->themearr[$this->theme]; + $n = count($this->data); + + if( $this->setslicecolors==null ) { + $numcolors=count($ta); + if( class_exists('PiePlot3D',false) && ($this instanceof PiePlot3D) ) { + $ta = array_reverse(array_slice($ta,0,$n)); + } + } + else { + $this->setslicecolors = array_slice($this->setslicecolors,0,$n); + $numcolors=count($this->setslicecolors); + if( $graph->pieaa && !($this instanceof PiePlot3D) ) { + $this->setslicecolors = array_reverse($this->setslicecolors); + } + } + + $sum=0; + for($i=0; $i < $n; ++$i) + $sum += $this->data[$i]; + + // Bail out with error if the sum is 0 + if( $sum==0 ) + JpGraphError::RaiseL(15009);//("Illegal pie plot. Sum of all data is zero for Pie!"); + + // Make sure we don't plot more values than data points + // (in case the user added more legends than data points) + $n = min(count($this->legends),count($this->data)); + if( $this->legends != "" ) { + $this->legends = array_reverse(array_slice($this->legends,0,$n)); + } + for( $i=$n-1; $i >= 0; --$i ) { + $l = $this->legends[$i]; + // Replace possible format with actual values + if( count($this->csimalts) > $i ) { + $fmt = $this->csimalts[$i]; + } + else { + $fmt = "%d"; // Deafult Alt if no other has been specified + } + if( $this->labeltype==0 ) { + $l = sprintf($l,100*$this->data[$i]/$sum); + $alt = sprintf($fmt,$this->data[$i]); + + } + elseif( $this->labeltype == 1) { + $l = sprintf($l,$this->data[$i]); + $alt = sprintf($fmt,$this->data[$i]); + + } + else { + $l = sprintf($l,$this->adjusted_data[$i]); + $alt = sprintf($fmt,$this->adjusted_data[$i]); + } + + if( empty($this->csimwintargets[$i]) ) { + $wintarg = ''; + } + else { + $wintarg = $this->csimwintargets[$i]; + } + + if( $this->setslicecolors==null ) { + $graph->legend->Add($l,$colors[$ta[$i%$numcolors]],"",0,$this->csimtargets[$i],$alt,$wintarg); + } + else { + $graph->legend->Add($l,$this->setslicecolors[$i%$numcolors],"",0,$this->csimtargets[$i],$alt,$wintarg); + } + } + } + + // Adjust the rounded percetage value so that the sum of + // of the pie slices are always 100% + // Using the Hare/Niemeyer method + function AdjPercentage($aData,$aPrec=0) { + $mul=100; + if( $aPrec > 0 && $aPrec < 3 ) { + if( $aPrec == 1 ) + $mul=1000; + else + $mul=10000; + } + + $tmp = array(); + $result = array(); + $quote_sum=0; + $n = count($aData) ; + for( $i=0, $sum=0; $i < $n; ++$i ) + $sum+=$aData[$i]; + foreach($aData as $index => $value) { + $tmp_percentage=$value/$sum*$mul; + $result[$index]=floor($tmp_percentage); + $tmp[$index]=$tmp_percentage-$result[$index]; + $quote_sum+=$result[$index]; + } + if( $quote_sum == $mul) { + if( $mul > 100 ) { + $tmp = $mul / 100; + for( $i=0; $i < $n; ++$i ) { + $result[$i] /= $tmp ; + } + } + return $result; + } + arsort($tmp,SORT_NUMERIC); + reset($tmp); + for($i=0; $i < $mul-$quote_sum; $i++) + { + $result[key($tmp)]++; + next($tmp); + } + if( $mul > 100 ) { + $tmp = $mul / 100; + for( $i=0; $i < $n; ++$i ) { + $result[$i] /= $tmp ; + } + } + return $result; + } + + + function Stroke($img,$aaoption=0) { + // aaoption is used to handle antialias + // aaoption == 0 a normal pie + // aaoption == 1 just the body + // aaoption == 2 just the values + + // Explode scaling. If anti anti alias we scale the image + // twice and we also need to scale the exploding distance + $expscale = $aaoption === 1 ? 2 : 1; + + if( $this->labeltype == 2 ) { + // Adjust the data so that it will add up to 100% + $this->adjusted_data = $this->AdjPercentage($this->data); + } + + $colors = array_keys($img->rgb->rgb_table); + sort($colors); + $ta=$this->themearr[$this->theme]; + $n = count($this->data); + + if( $this->setslicecolors==null ) { + $numcolors=count($ta); + } + else { + // We need to create an array of colors as long as the data + // since we need to reverse it to get the colors in the right order + $numcolors=count($this->setslicecolors); + $i = 2*$numcolors; + while( $n > $i ) { + $this->setslicecolors = array_merge($this->setslicecolors,$this->setslicecolors); + $i += $n; + } + $tt = array_slice($this->setslicecolors,0,$n % $numcolors); + $this->setslicecolors = array_merge($this->setslicecolors,$tt); + $this->setslicecolors = array_reverse($this->setslicecolors); + } + + // Draw the slices + $sum=0; + for($i=0; $i < $n; ++$i) + $sum += $this->data[$i]; + + // Bail out with error if the sum is 0 + if( $sum==0 ) + JpGraphError::RaiseL(15009);//("Sum of all data is 0 for Pie."); + + // Set up the pie-circle + if( $this->radius <= 1 ) + $radius = floor($this->radius*min($img->width,$img->height)); + else { + $radius = $aaoption === 1 ? $this->radius*2 : $this->radius; + } + + if( $this->posx <= 1 && $this->posx > 0 ) + $xc = round($this->posx*$img->width); + else + $xc = $this->posx ; + + if( $this->posy <= 1 && $this->posy > 0 ) + $yc = round($this->posy*$img->height); + else + $yc = $this->posy ; + + $n = count($this->data); + + if( $this->explode_all ) + for($i=0; $i < $n; ++$i) + $this->explode_radius[$i]=$this->explode_r; + + // If we have a shadow and not just drawing the labels + if( $this->ishadowcolor != "" && $aaoption !== 2) { + $accsum=0; + $angle2 = $this->startangle; + $img->SetColor($this->ishadowcolor); + for($i=0; $sum > 0 && $i < $n; ++$i) { + $j = $n-$i-1; + $d = $this->data[$i]; + $angle1 = $angle2; + $accsum += $d; + $angle2 = $this->startangle+2*M_PI*$accsum/$sum; + if( empty($this->explode_radius[$j]) ) + $this->explode_radius[$j]=0; + + if( $d < 0.00001 ) continue; + + $la = 2*M_PI - (abs($angle2-$angle1)/2.0+$angle1); + + $xcm = $xc + $this->explode_radius[$j]*cos($la)*$expscale; + $ycm = $yc - $this->explode_radius[$j]*sin($la)*$expscale; + + $xcm += $this->ishadowdrop*$expscale; + $ycm += $this->ishadowdrop*$expscale; + + $_sa = round($angle1*180/M_PI); + $_ea = round($angle2*180/M_PI); + + // The CakeSlice method draws a full circle in case of start angle = end angle + // for pie slices we don't want this behaviour unless we only have one + // slice in the pie in case it is the wanted behaviour + if( $_ea-$_sa > 0.1 || $n==1 ) { + $img->CakeSlice($xcm,$ycm,$radius-1,$radius-1, + $angle1*180/M_PI,$angle2*180/M_PI,$slicecolor,$arccolor); + } + } + } + + //-------------------------------------------------------------------------------- + // This is the main loop to draw each cake slice + //-------------------------------------------------------------------------------- + + // Set up the accumulated sum, start angle for first slice and border color + $accsum=0; + $angle2 = $this->startangle; + $img->SetColor($this->color); + + // Loop though all the slices if there is a pie to draw (sum>0) + // There are n slices in total + for($i=0; $sum>0 && $i < $n; ++$i) { + + // $j is the actual index used for the slice + $j = $n-$i-1; + + // Make sure we havea valid distance to explode the slice + if( empty($this->explode_radius[$j]) ) + $this->explode_radius[$j]=0; + + // The actual numeric value for the slice + $d = $this->data[$i]; + + $angle1 = $angle2; + + // Accumlate the sum + $accsum += $d; + + // The new angle when we add the "size" of this slice + // angle1 is then the start and angle2 the end of this slice + $angle2 = $this->NormAngle($this->startangle+2*M_PI*$accsum/$sum); + + // We avoid some trouble by not allowing end angle to be 0, in that case + // we translate to 360 + + + // la is used to hold the label angle, which is centered on the slice + if( $angle2 < 0.0001 && $angle1 > 0.0001 ) { + $this->la[$i] = 2*M_PI - (abs(2*M_PI-$angle1)/2.0+$angle1); + } + else + $this->la[$i] = 2*M_PI - (abs($angle2-$angle1)/2.0+$angle1); + + $_sa = round($angle1*180/M_PI); + $_ea = round($angle2*180/M_PI); + $_la = round($this->la[$i]*180/M_PI); + //echo "ang1=$_sa , ang2=$_ea - la=$_la
    "; + + // Too avoid rounding problems we skip the slice if it is too small + if( $d < 0.00001 ) continue; + + // If the user has specified an array of colors for each slice then use + // that a color otherwise use the theme array (ta) of colors + if( $this->setslicecolors==null ) + $slicecolor=$colors[$ta[$i%$numcolors]]; + else + $slicecolor=$this->setslicecolors[$i%$numcolors]; + + // If we have enabled antialias then we don't draw any border so + // make the bordedr color the same as the slice color + if( $this->pie_interior_border && $aaoption===0 ) + $img->SetColor($this->color); + else + $img->SetColor($slicecolor); + $arccolor = $this->pie_border && $aaoption===0 ? $this->color : ""; + + // Calculate the x,y coordinates for the base of this slice taking + // the exploded distance into account. Here we use the mid angle as the + // ray of extension and we have the mid angle handy as it is also the + // label angle + $xcm = $xc + $this->explode_radius[$j]*cos($this->la[$i])*$expscale; + $ycm = $yc - $this->explode_radius[$j]*sin($this->la[$i])*$expscale; + + // If we are not just drawing the labels then draw this cake slice + if( $aaoption !== 2 ) { + + + $_sa = round($angle1*180/M_PI); + $_ea = round($angle2*180/M_PI); + $_la = round($this->la[$i]*180/M_PI); + //echo "[$i] sa=$_sa, ea=$_ea, la[$i]=$_la, (color=$slicecolor)
    "; + + + // The CakeSlice method draws a full circle in case of start angle = end angle + // for pie slices we don't want this behaviour unless we only have one + // slice in the pie in case it is the wanted behaviour + if( abs($_ea-$_sa) > 0.1 || $n==1 ) { + $img->CakeSlice($xcm,$ycm,$radius-1,$radius-1,$_sa,$_ea,$slicecolor,$arccolor); + } + } + + // If the CSIM is used then make sure we register a CSIM area for this slice as well + if( $this->csimtargets && $aaoption !== 1 ) { + $this->AddSliceToCSIM($i,$xcm,$ycm,$radius,$angle1,$angle2); + } + } + + // Format the titles for each slice + if( $aaoption !== 2 ) { + for( $i=0; $i < $n; ++$i) { + if( $this->labeltype==0 ) { + if( $sum != 0 ) + $l = 100.0*$this->data[$i]/$sum; + else + $l = 0.0; + } + elseif( $this->labeltype==1 ) { + $l = $this->data[$i]*1.0; + } + else { + $l = $this->adjusted_data[$i]; + } + if( isset($this->labels[$i]) && is_string($this->labels[$i]) ) + $this->labels[$i]=sprintf($this->labels[$i],$l); + else + $this->labels[$i]=$l; + } + } + + if( $this->value->show && $aaoption !== 1 ) { + $this->StrokeAllLabels($img,$xc,$yc,$radius); + } + + // Adjust title position + if( $aaoption !== 1 ) { + $this->title->SetPos($xc, + $yc-$this->title->GetFontHeight($img)-$radius-$this->title->margin, + "center","bottom"); + $this->title->Stroke($img); + } + + } + +//--------------- +// PRIVATE METHODS + + function NormAngle($a) { + while( $a < 0 ) $a += 2*M_PI; + while( $a > 2*M_PI ) $a -= 2*M_PI; + return $a; + } + + function Quadrant($a) { + $a=$this->NormAngle($a); + if( $a > 0 && $a <= M_PI/2 ) + return 0; + if( $a > M_PI/2 && $a <= M_PI ) + return 1; + if( $a > M_PI && $a <= 1.5*M_PI ) + return 2; + if( $a > 1.5*M_PI ) + return 3; + } + + function StrokeGuideLabels($img,$xc,$yc,$radius) { + $n = count($this->labels); + + //----------------------------------------------------------------------- + // Step 1 of the algorithm is to construct a number of clusters + // a cluster is defined as all slices within the same quadrant (almost) + // that has an angular distance less than the treshold + //----------------------------------------------------------------------- + $tresh_hold=25 * M_PI/180; // 25 degrees difference to be in a cluster + $incluster=false; // flag if we are currently in a cluster or not + $clusters = array(); // array of clusters + $cidx=-1; // running cluster index + + // Go through all the labels and construct a number of clusters + for($i=0; $i < $n-1; ++$i) { + // Calc the angle distance between two consecutive slices + $a1=$this->la[$i]; + $a2=$this->la[$i+1]; + $q1 = $this->Quadrant($a1); + $q2 = $this->Quadrant($a2); + $diff = abs($a1-$a2); + if( $diff < $tresh_hold ) { + if( $incluster ) { + $clusters[$cidx][1]++; + // Each cluster can only cover one quadrant + // Do we cross a quadrant ( and must break the cluster) + if( $q1 != $q2 ) { + // If we cross a quadrant boundary we normally start a + // new cluster. However we need to take the 12'a clock + // and 6'a clock positions into a special consideration. + // Case 1: WE go from q=1 to q=2 if the last slice on + // the cluster for q=1 is close to 12'a clock and the + // first slice in q=0 is small we extend the previous + // cluster + if( $q1 == 1 && $q2 == 0 && $a2 > (90-15)*M_PI/180 ) { + if( $i < $n-2 ) { + $a3 = $this->la[$i+2]; + // If there isn't a cluster coming up with the next-next slice + // we extend the previous cluster to cover this slice as well + if( abs($a3-$a2) >= $tresh_hold ) { + $clusters[$cidx][1]++; + $i++; + } + } + } + elseif( $q1 == 3 && $q2 == 2 && $a2 > (270-15)*M_PI/180 ) { + if( $i < $n-2 ) { + $a3 = $this->la[$i+2]; + // If there isn't a cluster coming up with the next-next slice + // we extend the previous cluster to cover this slice as well + if( abs($a3-$a2) >= $tresh_hold ) { + $clusters[$cidx][1]++; + $i++; + } + } + } + + if( $q1==2 && $q2==1 && $a2 > (180-15)*M_PI/180 ) { + $clusters[$cidx][1]++; + $i++; + } + + $incluster = false; + } + } + elseif( $q1 == $q2) { + $incluster = true; + // Now we have a special case for quadrant 0. If we previously + // have a cluster of one in quadrant 0 we just extend that + // cluster. If we don't do this then we risk that the label + // for the cluster of one will cross the guide-line + if( $q1 == 0 && $cidx > -1 && + $clusters[$cidx][1] == 1 && + $this->Quadrant($this->la[$clusters[$cidx][0]]) == 0 ) { + $clusters[$cidx][1]++; + } + else { + $cidx++; + $clusters[$cidx][0] = $i; + $clusters[$cidx][1] = 1; + } + } + else { + // Create a "cluster" of one since we are just crossing + // a quadrant + $cidx++; + $clusters[$cidx][0] = $i; + $clusters[$cidx][1] = 1; + } + } + else { + if( $incluster ) { + // Add the last slice + $clusters[$cidx][1]++; + $incluster = false; + } + else { // Create a "cluster" of one + $cidx++; + $clusters[$cidx][0] = $i; + $clusters[$cidx][1] = 1; + } + } + } + // Handle the very last slice + if( $incluster ) { + $clusters[$cidx][1]++; + } + else { // Create a "cluster" of one + $cidx++; + $clusters[$cidx][0] = $i; + $clusters[$cidx][1] = 1; + } + + /* + if( true ) { + // Debug printout in labels + for( $i=0; $i <= $cidx; ++$i ) { + for( $j=0; $j < $clusters[$i][1]; ++$j ) { + $a = $this->la[$clusters[$i][0]+$j]; + $aa = round($a*180/M_PI); + $q = $this->Quadrant($a); + $this->labels[$clusters[$i][0]+$j]="[$q:$aa] $i:$j"; + } + } + } + */ + + //----------------------------------------------------------------------- + // Step 2 of the algorithm is use the clusters and draw the labels + // and guidelines + //----------------------------------------------------------------------- + + // We use the font height as the base factor for how far we need to + // spread the labels in the Y-direction. + $this->value->ApplyFont($img); + $fh = $img->GetFontHeight(); + $origvstep=$fh*$this->iGuideVFactor; + $this->value->SetMargin(0); + + // Number of clusters found + $nc = count($clusters); + + // Walk through all the clusters + for($i=0; $i < $nc; ++$i) { + + // Start angle and number of slices in this cluster + $csize = $clusters[$i][1]; + $a = $this->la[$clusters[$i][0]]; + $q = $this->Quadrant($a); + + // Now set up the start and end conditions to make sure that + // in each cluster we walk through the all the slices starting with the slice + // closest to the equator. Since all slices are numbered clockwise from "3'a clock" + // we have different conditions depending on in which quadrant the slice lies within. + if( $q == 0 ) { + $start = $csize-1; $idx = $start; $step = -1; $vstep = -$origvstep; + } + elseif( $q == 1 ) { + $start = 0; $idx = $start; $step = 1; $vstep = -$origvstep; + } + elseif( $q == 2 ) { + $start = $csize-1; $idx = $start; $step = -1; $vstep = $origvstep; + } + elseif( $q == 3 ) { + $start = 0; $idx = $start; $step = 1; $vstep = $origvstep; + } + + // Walk through all slices within this cluster + for($j=0; $j < $csize; ++$j) { + // Now adjust the position of the labels in each cluster starting + // with the slice that is closest to the equator of the pie + $a = $this->la[$clusters[$i][0]+$idx]; + + // Guide line start in the center of the arc of the slice + $r = $radius+$this->explode_radius[$n-1-($clusters[$i][0]+$idx)]; + $x = round($r*cos($a)+$xc); + $y = round($yc-$r*sin($a)); + + // The distance from the arc depends on chosen font and the "R-Factor" + $r += $fh*$this->iGuideLineRFactor; + + // Should the labels be placed curved along the pie or in straight columns + // outside the pie? + if( $this->iGuideLineCurve ) + $xt=round($r*cos($a)+$xc); + + // If this is the first slice in the cluster we need some first time + // proessing + if( $idx == $start ) { + if( ! $this->iGuideLineCurve ) + $xt=round($r*cos($a)+$xc); + $yt=round($yc-$r*sin($a)); + + // Some special consideration in case this cluster starts + // in quadrant 1 or 3 very close to the "equator" (< 20 degrees) + // and the previous clusters last slice is within the tolerance. + // In that case we add a font height to this labels Y-position + // so it doesn't collide with + // the slice in the previous cluster + $prevcluster = ($i + ($nc-1) ) % $nc; + $previdx=$clusters[$prevcluster][0]+$clusters[$prevcluster][1]-1; + if( $q == 1 && $a > 160*M_PI/180 ) { + // Get the angle for the previous clusters last slice + $diff = abs($a-$this->la[$previdx]); + if( $diff < $tresh_hold ) { + $yt -= $fh; + } + } + elseif( $q == 3 && $a > 340*M_PI/180 ) { + // We need to subtract 360 to compare angle distance between + // q=0 and q=3 + $diff = abs($a-$this->la[$previdx]-360*M_PI/180); + if( $diff < $tresh_hold ) { + $yt += $fh; + } + } + + } + else { + // The step is at minimum $vstep but if the slices are relatively large + // we make sure that we add at least a step that corresponds to the vertical + // distance between the centers at the arc on the slice + $prev_a = $this->la[$clusters[$i][0]+($idx-$step)]; + $dy = abs($radius*(sin($a)-sin($prev_a))*1.2); + if( $vstep > 0 ) + $yt += max($vstep,$dy); + else + $yt += min($vstep,-$dy); + } + + $label = $this->labels[$clusters[$i][0]+$idx]; + + if( $csize == 1 ) { + // A "meta" cluster with only one slice + $r = $radius+$this->explode_radius[$n-1-($clusters[$i][0]+$idx)]; + $rr = $r+$img->GetFontHeight()/2; + $xt=round($rr*cos($a)+$xc); + $yt=round($yc-$rr*sin($a)); + $this->StrokeLabel($label,$img,$xc,$yc,$a,$r); + if( $this->iShowGuideLineForSingle ) + $this->guideline->Stroke($img,$x,$y,$xt,$yt); + } + else { + $this->guideline->Stroke($img,$x,$y,$xt,$yt); + if( $q==1 || $q==2 ) { + // Left side of Pie + $this->guideline->Stroke($img,$xt,$yt,$xt-$this->guidelinemargin,$yt); + $lbladj = -$this->guidelinemargin-5; + $this->value->halign = "right"; + $this->value->valign = "center"; + } + else { + // Right side of pie + $this->guideline->Stroke($img,$xt,$yt,$xt+$this->guidelinemargin,$yt); + $lbladj = $this->guidelinemargin+5; + $this->value->halign = "left"; + $this->value->valign = "center"; + } + $this->value->Stroke($img,$label,$xt+$lbladj,$yt); + } + + // Udate idx to point to next slice in the cluster to process + $idx += $step; + } + } + } + + function StrokeAllLabels($img,$xc,$yc,$radius) { + // First normalize all angles for labels + $n = count($this->la); + for($i=0; $i < $n; ++$i) { + $this->la[$i] = $this->NormAngle($this->la[$i]); + } + if( $this->guideline->iShow ) { + $this->StrokeGuideLabels($img,$xc,$yc,$radius); + } + else { + $n = count($this->labels); + for($i=0; $i < $n; ++$i) { + $this->StrokeLabel($this->labels[$i],$img,$xc,$yc, + $this->la[$i], + $radius + $this->explode_radius[$n-1-$i]); + } + } + } + + // Position the labels of each slice + function StrokeLabel($label,$img,$xc,$yc,$a,$r) { + + // Default value + if( $this->ilabelposadj === 'auto' ) + $this->ilabelposadj = 0.65; + + // We position the values diferently depending on if they are inside + // or outside the pie + if( $this->ilabelposadj < 1.0 ) { + + $this->value->SetAlign('center','center'); + $this->value->margin = 0; + + $xt=round($this->ilabelposadj*$r*cos($a)+$xc); + $yt=round($yc-$this->ilabelposadj*$r*sin($a)); + + $this->value->Stroke($img,$label,$xt,$yt); + } + else { + + $this->value->halign = "left"; + $this->value->valign = "top"; + $this->value->margin = 0; + + // Position the axis title. + // dx, dy is the offset from the top left corner of the bounding box that sorrounds the text + // that intersects with the extension of the corresponding axis. The code looks a little + // bit messy but this is really the only way of having a reasonable position of the + // axis titles. + $this->value->ApplyFont($img); + $h=$img->GetTextHeight($label); + // For numeric values the format of the display value + // must be taken into account + if( is_numeric($label) ) { + if( $label > 0 ) + $w=$img->GetTextWidth(sprintf($this->value->format,$label)); + else + $w=$img->GetTextWidth(sprintf($this->value->negformat,$label)); + } + else + $w=$img->GetTextWidth($label); + + if( $this->ilabelposadj > 1.0 && $this->ilabelposadj < 5.0) { + $r *= $this->ilabelposadj; + } + + $r += $img->GetFontHeight()/1.5; + + $xt=round($r*cos($a)+$xc); + $yt=round($yc-$r*sin($a)); + + // Normalize angle + while( $a < 0 ) $a += 2*M_PI; + while( $a > 2*M_PI ) $a -= 2*M_PI; + + if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0; + if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI; + if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1; + if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI); + + if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI; + if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI); + if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1; + if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI); + if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0; + + $this->value->Stroke($img,$label,$xt-$dx*$w,$yt-$dy*$h); + } + } +} // Class + + +//=================================================== +// CLASS PiePlotC +// Description: Same as a normal pie plot but with a +// filled circle in the center +//=================================================== +class PiePlotC extends PiePlot { + private $imidsize=0.5; // Fraction of total width + private $imidcolor='white'; + public $midtitle=''; + private $middlecsimtarget='',$middlecsimwintarget='',$middlecsimalt=''; + + function PiePlotC($data,$aCenterTitle='') { + parent::PiePlot($data); + $this->midtitle = new Text(); + $this->midtitle->ParagraphAlign('center'); + } + + function SetMid($aTitle,$aColor='white',$aSize=0.5) { + $this->midtitle->Set($aTitle); + + $this->imidsize = $aSize ; + $this->imidcolor = $aColor ; + } + + function SetMidTitle($aTitle) { + $this->midtitle->Set($aTitle); + } + + function SetMidSize($aSize) { + $this->imidsize = $aSize ; + } + + function SetMidColor($aColor) { + $this->imidcolor = $aColor ; + } + + function SetMidCSIM($aTarget,$aAlt='',$aWinTarget='') { + $this->middlecsimtarget = $aTarget; + $this->middlecsimwintarget = $aWinTarget; + $this->middlecsimalt = $aAlt; + } + + function AddSliceToCSIM($i,$xc,$yc,$radius,$sa,$ea) { + //Slice number, ellipse centre (x,y), radius, start angle, end angle + while( $sa > 2*M_PI ) $sa = $sa - 2*M_PI; + while( $ea > 2*M_PI ) $ea = $ea - 2*M_PI; + + $sa = 2*M_PI - $sa; + $ea = 2*M_PI - $ea; + + // Special case when we have only one slice since then both start and end + // angle will be == 0 + if( abs($sa - $ea) < 0.0001 ) { + $sa=2*M_PI; $ea=0; + } + + // Add inner circle first point + $xp = floor(($this->imidsize*$radius*cos($ea))+$xc); + $yp = floor($yc-($this->imidsize*$radius*sin($ea))); + $coords = "$xp, $yp"; + + //add coordinates every 0.25 radians + $a=$ea+0.25; + + // If we cross the 360-limit with a slice we need to handle + // the fact that end angle is smaller than start + if( $sa < $ea ) { + while ($a <= 2*M_PI) { + $xp = floor($radius*cos($a)+$xc); + $yp = floor($yc-$radius*sin($a)); + $coords.= ", $xp, $yp"; + $a += 0.25; + } + $a -= 2*M_PI; + } + + while ($a < $sa) { + $xp = floor(($this->imidsize*$radius*cos($a)+$xc)); + $yp = floor($yc-($this->imidsize*$radius*sin($a))); + $coords.= ", $xp, $yp"; + $a += 0.25; + } + + // Make sure we end at the last point + $xp = floor(($this->imidsize*$radius*cos($sa)+$xc)); + $yp = floor($yc-($this->imidsize*$radius*sin($sa))); + $coords.= ", $xp, $yp"; + + // Straight line to outer circle + $xp = floor($radius*cos($sa)+$xc); + $yp = floor($yc-$radius*sin($sa)); + $coords.= ", $xp, $yp"; + + //add coordinates every 0.25 radians + $a=$sa - 0.25; + while ($a > $ea) { + $xp = floor($radius*cos($a)+$xc); + $yp = floor($yc-$radius*sin($a)); + $coords.= ", $xp, $yp"; + $a -= 0.25; + } + + //Add the last point on the arc + $xp = floor($radius*cos($ea)+$xc); + $yp = floor($yc-$radius*sin($ea)); + $coords.= ", $xp, $yp"; + + // Close the arc + $xp = floor(($this->imidsize*$radius*cos($ea))+$xc); + $yp = floor($yc-($this->imidsize*$radius*sin($ea))); + $coords .= ", $xp, $yp"; + + if( !empty($this->csimtargets[$i]) ) { + $this->csimareas .= "csimtargets[$i]."\""; + if( !empty($this->csimwintargets[$i]) ) { + $this->csimareas .= " target=\"".$this->csimwintargets[$i]."\" "; + } + if( !empty($this->csimalts[$i]) ) { + $tmp=sprintf($this->csimalts[$i],$this->data[$i]); + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + } + + + function Stroke($img,$aaoption=0) { + + // Stroke the pie but don't stroke values + $tmp = $this->value->show; + $this->value->show = false; + parent::Stroke($img,$aaoption); + $this->value->show = $tmp; + + $xc = round($this->posx*$img->width); + $yc = round($this->posy*$img->height); + + $radius = floor($this->radius * min($img->width,$img->height)) ; + + + if( $this->imidsize > 0 && $aaoption !== 2 ) { + + if( $this->ishadowcolor != "" ) { + $img->SetColor($this->ishadowcolor); + $img->FilledCircle($xc+$this->ishadowdrop,$yc+$this->ishadowdrop, + round($radius*$this->imidsize)); + } + + $img->SetColor($this->imidcolor); + $img->FilledCircle($xc,$yc,round($radius*$this->imidsize)); + + if( $this->pie_border && $aaoption === 0 ) { + $img->SetColor($this->color); + $img->Circle($xc,$yc,round($radius*$this->imidsize)); + } + + if( !empty($this->middlecsimtarget) ) + $this->AddMiddleCSIM($xc,$yc,round($radius*$this->imidsize)); + + } + + if( $this->value->show && $aaoption !== 1) { + $this->StrokeAllLabels($img,$xc,$yc,$radius); + $this->midtitle->SetPos($xc,$yc,'center','center'); + $this->midtitle->Stroke($img); + } + + } + + function AddMiddleCSIM($xc,$yc,$r) { + $xc=round($xc);$yc=round($yc);$r=round($r); + $this->csimareas .= "middlecsimtarget."\""; + if( !empty($this->middlecsimwintarget) ) { + $this->csimareas .= " target=\"".$this->middlecsimwintarget."\""; + } + if( !empty($this->middlecsimalt) ) { + $tmp = $this->middlecsimalt; + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + + function StrokeLabel($label,$img,$xc,$yc,$a,$r) { + + if( $this->ilabelposadj === 'auto' ) + $this->ilabelposadj = (1-$this->imidsize)/2+$this->imidsize; + + parent::StrokeLabel($label,$img,$xc,$yc,$a,$r); + + } + +} + + +//=================================================== +// CLASS PieGraph +// Description: +//=================================================== +class PieGraph extends Graph { + private $posx, $posy, $radius; + private $legends=array(); + public $plots=array(); + public $pieaa = false ; +//--------------- +// CONSTRUCTOR + function PieGraph($width=300,$height=200,$cachedName="",$timeout=0,$inline=1) { + $this->Graph($width,$height,$cachedName,$timeout,$inline); + $this->posx=$width/2; + $this->posy=$height/2; + $this->SetColor(array(255,255,255)); + } + +//--------------- +// PUBLIC METHODS + function Add($aObj) { + + if( is_array($aObj) && count($aObj) > 0 ) + $cl = $aObj[0]; + else + $cl = $aObj; + + if( $cl instanceof Text ) + $this->AddText($aObj); + elseif( class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) + $this->AddIcon($aObj); + else { + if( is_array($aObj) ) { + $n = count($aObj); + for($i=0; $i < $n; ++$i ) { + $this->plots[] = $aObj[$i]; + } + } + else { + $this->plots[] = $aObj; + } + } + } + + function SetAntiAliasing($aFlg=true) { + $this->pieaa = $aFlg; + } + + function SetColor($c) { + $this->SetMarginColor($c); + } + + + function DisplayCSIMAreas() { + $csim=""; + foreach($this->plots as $p ) { + $csim .= $p->GetCSIMareas(); + } + //$csim.= $this->legend->GetCSIMareas(); + if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) { + $this->img->SetColor($this->csimcolor); + $n = count($coords[0]); + for ($i=0; $i < $n; $i++) { + if ($coords[1][$i]=="poly") { + preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts); + $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]); + $m = count($pts[0]); + for ($j=0; $j < $m; $j++) { + $this->img->LineTo($pts[1][$j],$pts[2][$j]); + } + } else if ($coords[1][$i]=="rect") { + $pts = preg_split('/,/', $coords[2][$i]); + $this->img->SetStartPoint($pts[0],$pts[1]); + $this->img->LineTo($pts[2],$pts[1]); + $this->img->LineTo($pts[2],$pts[3]); + $this->img->LineTo($pts[0],$pts[3]); + $this->img->LineTo($pts[0],$pts[1]); + + } + } + } + } + + // Method description + function Stroke($aStrokeFileName="") { + // If the filename is the predefined value = '_csim_special_' + // we assume that the call to stroke only needs to do enough + // to correctly generate the CSIM maps. + // We use this variable to skip things we don't strictly need + // to do to generate the image map to improve performance + // a best we can. Therefor you will see a lot of tests !$_csim in the + // code below. + $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE); + + // We need to know if we have stroked the plot in the + // GetCSIMareas. Otherwise the CSIM hasn't been generated + // and in the case of GetCSIM called before stroke to generate + // CSIM without storing an image to disk GetCSIM must call Stroke. + $this->iHasStroked = true; + + $n = count($this->plots); + + if( $this->pieaa ) { + + if( !$_csim ) { + if( $this->background_image != "" ) { + $this->StrokeFrameBackground(); + } + else { + $this->StrokeFrame(); + $this->StrokeBackgroundGrad(); + } + } + + + $w = $this->img->width; + $h = $this->img->height; + $oldimg = $this->img->img; + + $this->img->CreateImgCanvas(2*$w,2*$h); + + $this->img->SetColor( $this->margin_color ); + $this->img->FilledRectangle(0,0,2*$w-1,2*$h-1); + + // Make all icons *2 i size since we will be scaling down the + // imahe to do the anti aliasing + $ni = count($this->iIcons); + for($i=0; $i < $ni; ++$i) { + $this->iIcons[$i]->iScale *= 2 ; + if( $this->iIcons[$i]->iX > 1 ) + $this->iIcons[$i]->iX *= 2 ; + if( $this->iIcons[$i]->iY > 1 ) + $this->iIcons[$i]->iY *= 2 ; + } + + $this->StrokeIcons(); + + for($i=0; $i < $n; ++$i) { + if( $this->plots[$i]->posx > 1 ) + $this->plots[$i]->posx *= 2 ; + if( $this->plots[$i]->posy > 1 ) + $this->plots[$i]->posy *= 2 ; + + $this->plots[$i]->Stroke($this->img,1); + + if( $this->plots[$i]->posx > 1 ) + $this->plots[$i]->posx /= 2 ; + if( $this->plots[$i]->posy > 1 ) + $this->plots[$i]->posy /= 2 ; + } + + $indent = $this->doframe ? ($this->frame_weight + ($this->doshadow ? $this->shadow_width : 0 )) : 0 ; + $indent += $this->framebevel ? $this->framebeveldepth + 1 : 0 ; + $this->img->CopyCanvasH($oldimg,$this->img->img,$indent,$indent,$indent,$indent, + $w-2*$indent,$h-2*$indent,2*($w-$indent),2*($h-$indent)); + + $this->img->img = $oldimg ; + $this->img->width = $w ; + $this->img->height = $h ; + + for($i=0; $i < $n; ++$i) { + $this->plots[$i]->Stroke($this->img,2); // Stroke labels + $this->plots[$i]->Legend($this); + } + + } + else { + + if( !$_csim ) { + if( $this->background_image != "" ) { + $this->StrokeFrameBackground(); + } + else { + $this->StrokeFrame(); + } + } + + $this->StrokeIcons(); + + for($i=0; $i < $n; ++$i) { + $this->plots[$i]->Stroke($this->img); + $this->plots[$i]->Legend($this); + } + } + + $this->legend->Stroke($this->img); + $this->footer->Stroke($this->img); + $this->StrokeTitles(); + + if( !$_csim ) { + + // Stroke texts + if( $this->texts != null ) { + $n = count($this->texts); + for($i=0; $i < $n; ++$i ) { + $this->texts[$i]->Stroke($this->img); + } + } + + if( _JPG_DEBUG ) { + $this->DisplayCSIMAreas(); + } + + // Should we do any final image transformation + if( $this->iImgTrans ) { + if( !class_exists('ImgTrans',false) ) { + require_once('jpgraph_imgtrans.php'); + //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.'); + } + + $tform = new ImgTrans($this->img->img); + $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist, + $this->iImgTransDirection,$this->iImgTransHighQ, + $this->iImgTransMinSize,$this->iImgTransFillColor, + $this->iImgTransBorder); + } + + + // If the filename is given as the special "__handle" + // then the image handler is returned and the image is NOT + // streamed back + if( $aStrokeFileName == _IMG_HANDLER ) { + return $this->img->img; + } + else { + // Finally stream the generated picture + $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline, + $aStrokeFileName); + } + } + } +} // Class + +/* EOF */ +?> diff --git a/src/classes/jpgraph/jpgraph_pie3d.php b/src/classes/jpgraph/jpgraph_pie3d.php new file mode 100644 index 0000000..8b6c1f4 --- /dev/null +++ b/src/classes/jpgraph/jpgraph_pie3d.php @@ -0,0 +1,923 @@ +radius = 0.5; + $this->data = $data; + $this->title = new Text(""); + $this->title->SetFont(FF_FONT1,FS_BOLD); + $this->value = new DisplayValue(); + $this->value->Show(); + $this->value->SetFormat('%.0f%%'); + } + +//--------------- +// PUBLIC METHODS + + // Set label arrays + function SetLegends($aLegend) { + $this->legends = array_reverse(array_slice($aLegend,0,count($this->data))); + } + + function SetSliceColors($aColors) { + $this->setslicecolors = $aColors; + } + + function Legend($aGraph) { + parent::Legend($aGraph); + $aGraph->legend->txtcol = array_reverse($aGraph->legend->txtcol); + } + + function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') { + $this->csimtargets = $aTargets; + $this->csimwintargets = $aWinTargets; + $this->csimalts = $aAlts; + } + + // Should the slices be separated by a line? If color is specified as "" no line + // will be used to separate pie slices. + function SetEdge($aColor='black',$aWeight=1) { + $this->edgecolor = $aColor; + $this->edgeweight = $aWeight; + } + + // Dummy function to make Pie3D behave in a similair way to 2D + function ShowBorder($exterior=true,$interior=true) { + JpGraphError::RaiseL(14001); +//('Pie3D::ShowBorder() . Deprecated function. Use Pie3D::SetEdge() to control the edges around slices.'); + } + + // Specify projection angle for 3D in degrees + // Must be between 20 and 70 degrees + function SetAngle($a) { + if( $a<5 || $a>90 ) + JpGraphError::RaiseL(14002); +//("PiePlot3D::SetAngle() 3D Pie projection angle must be between 5 and 85 degrees."); + else + $this->angle = $a; + } + + function Add3DSliceToCSIM($i,$xc,$yc,$height,$width,$thick,$sa,$ea) { //Slice number, ellipse centre (x,y), height, width, start angle, end angle + + $sa *= M_PI/180; + $ea *= M_PI/180; + + //add coordinates of the centre to the map + $coords = "$xc, $yc"; + + //add coordinates of the first point on the arc to the map + $xp = floor($width*cos($sa)/2+$xc); + $yp = floor($yc-$height*sin($sa)/2); + $coords.= ", $xp, $yp"; + + //If on the front half, add the thickness offset + if ($sa >= M_PI && $sa <= 2*M_PI*1.01) { + $yp = floor($yp+$thick); + $coords.= ", $xp, $yp"; + } + + //add coordinates every 0.2 radians + $a=$sa+0.2; + while ($a<$ea) { + $xp = floor($width*cos($a)/2+$xc); + if ($a >= M_PI && $a <= 2*M_PI*1.01) { + $yp = floor($yc-($height*sin($a)/2)+$thick); + } else { + $yp = floor($yc-$height*sin($a)/2); + } + $coords.= ", $xp, $yp"; + $a += 0.2; + } + + //Add the last point on the arc + $xp = floor($width*cos($ea)/2+$xc); + $yp = floor($yc-$height*sin($ea)/2); + + + if ($ea >= M_PI && $ea <= 2*M_PI*1.01) { + $coords.= ", $xp, ".floor($yp+$thick); + } + $coords.= ", $xp, $yp"; + $alt=''; + + if( !empty($this->csimtargets[$i]) ) { + $this->csimareas .= "csimtargets[$i]."\""; + + if( !empty($this->csimwintargets[$i]) ) { + $this->csimareas .= " target=\"".$this->csimwintargets[$i]."\" "; + } + + if( !empty($this->csimalts[$i]) ) { + $tmp=sprintf($this->csimalts[$i],$this->data[$i]); + $this->csimareas .= "alt=\"$tmp\" title=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + + } + + function SetLabels($aLabels,$aLblPosAdj="auto") { + $this->labels = $aLabels; + $this->ilabelposadj=$aLblPosAdj; + } + + + // Distance from the pie to the labels + function SetLabelMargin($m) { + $this->value->SetMargin($m); + } + + // Show a thin line from the pie to the label for a specific slice + function ShowLabelHint($f=true) { + $this->showlabelhint=$f; + } + + // Set color of hint line to label for each slice + function SetLabelHintColor($c) { + $this->labelhintcolor=$c; + } + + function SetHeight($aHeight) { + $this->iThickness = $aHeight; + } + + +// Normalize Angle between 0-360 + function NormAngle($a) { + // Normalize anle to 0 to 2M_PI + // + if( $a > 0 ) { + while($a > 360) $a -= 360; + } + else { + while($a < 0) $a += 360; + } + if( $a < 0 ) + $a = 360 + $a; + + if( $a == 360 ) $a=0; + return $a; + } + + + +// Draw one 3D pie slice at position ($xc,$yc) with height $z + function Pie3DSlice($img,$xc,$yc,$w,$h,$sa,$ea,$z,$fillcolor,$shadow=0.65) { + + // Due to the way the 3D Pie algorithm works we are + // guaranteed that any slice we get into this method + // belongs to either the left or right side of the + // pie ellipse. Hence, no slice will cross 90 or 270 + // point. + if( ($sa < 90 && $ea > 90) || ( ($sa > 90 && $sa < 270) && $ea > 270) ) { + JpGraphError::RaiseL(14003);//('Internal assertion failed. Pie3D::Pie3DSlice'); + exit(1); + } + + $p[] = array(); + + // Setup pre-calculated values + $rsa = $sa/180*M_PI; // to Rad + $rea = $ea/180*M_PI; // to Rad + $sinsa = sin($rsa); + $cossa = cos($rsa); + $sinea = sin($rea); + $cosea = cos($rea); + + // p[] is the points for the overall slice and + // pt[] is the points for the top pie + + // Angular step when approximating the arc with a polygon train. + $step = 0.05; + + if( $sa >= 270 ) { + if( $ea > 360 || ($ea > 0 && $ea <= 90) ) { + if( $ea > 0 && $ea <= 90 ) { + // Adjust angle to simplify conditions in loops + $rea += 2*M_PI; + } + + $p = array($xc,$yc,$xc,$yc+$z, + $xc+$w*$cossa,$z+$yc-$h*$sinsa); + $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa); + + for( $a=$rsa; $a < 2*M_PI; $a += $step ) { + $tca = cos($a); + $tsa = sin($a); + $p[] = $xc+$w*$tca; + $p[] = $z+$yc-$h*$tsa; + $pt[] = $xc+$w*$tca; + $pt[] = $yc-$h*$tsa; + } + + $pt[] = $xc+$w; + $pt[] = $yc; + + $p[] = $xc+$w; + $p[] = $z+$yc; + $p[] = $xc+$w; + $p[] = $yc; + $p[] = $xc; + $p[] = $yc; + + for( $a=2*M_PI+$step; $a < $rea; $a += $step ) { + $pt[] = $xc + $w*cos($a); + $pt[] = $yc - $h*sin($a); + } + + $pt[] = $xc+$w*$cosea; + $pt[] = $yc-$h*$sinea; + $pt[] = $xc; + $pt[] = $yc; + + } + else { + $p = array($xc,$yc,$xc,$yc+$z, + $xc+$w*$cossa,$z+$yc-$h*$sinsa); + $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa); + + $rea = $rea == 0.0 ? 2*M_PI : $rea; + for( $a=$rsa; $a < $rea; $a += $step ) { + $tca = cos($a); + $tsa = sin($a); + $p[] = $xc+$w*$tca; + $p[] = $z+$yc-$h*$tsa; + $pt[] = $xc+$w*$tca; + $pt[] = $yc-$h*$tsa; + } + + $pt[] = $xc+$w*$cosea; + $pt[] = $yc-$h*$sinea; + $pt[] = $xc; + $pt[] = $yc; + + $p[] = $xc+$w*$cosea; + $p[] = $z+$yc-$h*$sinea; + $p[] = $xc+$w*$cosea; + $p[] = $yc-$h*$sinea; + $p[] = $xc; + $p[] = $yc; + } + } + elseif( $sa >= 180 ) { + $p = array($xc,$yc,$xc,$yc+$z,$xc+$w*$cosea,$z+$yc-$h*$sinea); + $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea); + + for( $a=$rea; $a>$rsa; $a -= $step ) { + $tca = cos($a); + $tsa = sin($a); + $p[] = $xc+$w*$tca; + $p[] = $z+$yc-$h*$tsa; + $pt[] = $xc+$w*$tca; + $pt[] = $yc-$h*$tsa; + } + + $pt[] = $xc+$w*$cossa; + $pt[] = $yc-$h*$sinsa; + $pt[] = $xc; + $pt[] = $yc; + + $p[] = $xc+$w*$cossa; + $p[] = $z+$yc-$h*$sinsa; + $p[] = $xc+$w*$cossa; + $p[] = $yc-$h*$sinsa; + $p[] = $xc; + $p[] = $yc; + + } + elseif( $sa >= 90 ) { + if( $ea > 180 ) { + $p = array($xc,$yc,$xc,$yc+$z,$xc+$w*$cosea,$z+$yc-$h*$sinea); + $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea); + + for( $a=$rea; $a > M_PI; $a -= $step ) { + $tca = cos($a); + $tsa = sin($a); + $p[] = $xc+$w*$tca; + $p[] = $z + $yc - $h*$tsa; + $pt[] = $xc+$w*$tca; + $pt[] = $yc-$h*$tsa; + } + + $p[] = $xc-$w; + $p[] = $z+$yc; + $p[] = $xc-$w; + $p[] = $yc; + $p[] = $xc; + $p[] = $yc; + + $pt[] = $xc-$w; + $pt[] = $z+$yc; + $pt[] = $xc-$w; + $pt[] = $yc; + + for( $a=M_PI-$step; $a > $rsa; $a -= $step ) { + $pt[] = $xc + $w*cos($a); + $pt[] = $yc - $h*sin($a); + } + + $pt[] = $xc+$w*$cossa; + $pt[] = $yc-$h*$sinsa; + $pt[] = $xc; + $pt[] = $yc; + + } + else { // $sa >= 90 && $ea <= 180 + $p = array($xc,$yc,$xc,$yc+$z, + $xc+$w*$cosea,$z+$yc-$h*$sinea, + $xc+$w*$cosea,$yc-$h*$sinea, + $xc,$yc); + + $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea); + + for( $a=$rea; $a>$rsa; $a -= $step ) { + $pt[] = $xc + $w*cos($a); + $pt[] = $yc - $h*sin($a); + } + + $pt[] = $xc+$w*$cossa; + $pt[] = $yc-$h*$sinsa; + $pt[] = $xc; + $pt[] = $yc; + + } + } + else { // sa > 0 && ea < 90 + + $p = array($xc,$yc,$xc,$yc+$z, + $xc+$w*$cossa,$z+$yc-$h*$sinsa, + $xc+$w*$cossa,$yc-$h*$sinsa, + $xc,$yc); + + $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa); + + for( $a=$rsa; $a < $rea; $a += $step ) { + $pt[] = $xc + $w*cos($a); + $pt[] = $yc - $h*sin($a); + } + + $pt[] = $xc+$w*$cosea; + $pt[] = $yc-$h*$sinea; + $pt[] = $xc; + $pt[] = $yc; + } + + $img->PushColor($fillcolor.":".$shadow); + $img->FilledPolygon($p); + $img->PopColor(); + + $img->PushColor($fillcolor); + $img->FilledPolygon($pt); + $img->PopColor(); + } + + function SetStartAngle($aStart) { + if( $aStart < 0 || $aStart > 360 ) { + JpGraphError::RaiseL(14004);//('Slice start angle must be between 0 and 360 degrees.'); + } + $this->startangle = $aStart; + } + +// Draw a 3D Pie + function Pie3D($aaoption,$img,$data,$colors,$xc,$yc,$d,$angle,$z, + $shadow=0.65,$startangle=0,$edgecolor="",$edgeweight=1) { + + //--------------------------------------------------------------------------- + // As usual the algorithm get more complicated than I originally + // envisioned. I believe that this is as simple as it is possible + // to do it with the features I want. It's a good exercise to start + // thinking on how to do this to convince your self that all this + // is really needed for the general case. + // + // The algorithm two draw 3D pies without "real 3D" is done in + // two steps. + // First imagine the pie cut in half through a thought line between + // 12'a clock and 6'a clock. It now easy to imagine that we can plot + // the individual slices for each half by starting with the topmost + // pie slice and continue down to 6'a clock. + // + // In the algortithm this is done in three principal steps + // Step 1. Do the knife cut to ensure by splitting slices that extends + // over the cut line. This is done by splitting the original slices into + // upto 3 subslices. + // Step 2. Find the top slice for each half + // Step 3. Draw the slices from top to bottom + // + // The thing that slightly complicates this scheme with all the + // angle comparisons below is that we can have an arbitrary start + // angle so we must take into account the different equivalence classes. + // For the same reason we must walk through the angle array in a + // modulo fashion. + // + // Limitations of algorithm: + // * A small exploded slice which crosses the 270 degree point + // will get slightly nagged close to the center due to the fact that + // we print the slices in Z-order and that the slice left part + // get printed first and might get slightly nagged by a larger + // slice on the right side just before the right part of the small + // slice. Not a major problem though. + //--------------------------------------------------------------------------- + + + // Determine the height of the ellippse which gives an + // indication of the inclination angle + $h = ($angle/90.0)*$d; + $sum = 0; + for($i=0; $ilabeltype == 2 ) { + $this->adjusted_data = $this->AdjPercentage($data); + } + + // Setup the start + $accsum = 0; + $a = $startangle; + $a = $this->NormAngle($a); + + // + // Step 1 . Split all slices that crosses 90 or 270 + // + $idx=0; + $adjexplode=array(); + $numcolors = count($colors); + for($i=0; $iexplode_radius[$i]) ) + $this->explode_radius[$i]=0; + + $expscale=1; + if( $aaoption == 1 ) + $expscale=2; + + $la = $a + $da/2; + $explode = array( $xc + $this->explode_radius[$i]*cos($la*M_PI/180)*$expscale, + $yc - $this->explode_radius[$i]*sin($la*M_PI/180) * ($h/$d) *$expscale ); + $adjexplode[$idx] = $explode; + $labeldata[$i] = array($la,$explode[0],$explode[1]); + $originalangles[$i] = array($a,$a+$da); + + $ne = $this->NormAngle($a+$da); + if( $da <= 180 ) { + // If the slice size is <= 90 it can at maximum cut across + // one boundary (either 90 or 270) where it needs to be split + $split=-1; // no split + if( ($da<=90 && ($a <= 90 && $ne > 90)) || + (($da <= 180 && $da >90) && (($a < 90 || $a >= 270) && $ne > 90)) ) { + $split = 90; + } + elseif( ($da<=90 && ($a <= 270 && $ne > 270)) || + (($da<=180 && $da>90) && ($a >= 90 && $a < 270 && ($a+$da) > 270 )) ) { + $split = 270; + } + if( $split > 0 ) { // split in two + $angles[$idx] = array($a,$split); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + $angles[++$idx] = array($split,$ne); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + } + else { // no split + $angles[$idx] = array($a,$ne); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + } + } + else { + // da>180 + // Slice may, depending on position, cross one or two + // bonudaries + + if( $a < 90 ) + $split = 90; + elseif( $a <= 270 ) + $split = 270; + else + $split = 90; + + $angles[$idx] = array($a,$split); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + //if( $a+$da > 360-$split ) { + // For slices larger than 270 degrees we might cross + // another boundary as well. This means that we must + // split the slice further. The comparison gets a little + // bit complicated since we must take into accound that + // a pie might have a startangle >0 and hence a slice might + // wrap around the 0 angle. + // Three cases: + // a) Slice starts before 90 and hence gets a split=90, but + // we must also check if we need to split at 270 + // b) Slice starts after 90 but before 270 and slices + // crosses 90 (after a wrap around of 0) + // c) If start is > 270 (hence the firstr split is at 90) + // and the slice is so large that it goes all the way + // around 270. + if( ($a < 90 && ($a+$da > 270)) || + ($a > 90 && $a<=270 && ($a+$da>360+90) ) || + ($a > 270 && $this->NormAngle($a+$da)>270) ) { + $angles[++$idx] = array($split,360-$split); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + $angles[++$idx] = array(360-$split,$ne); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + } + else { + // Just a simple split to the previous decided + // angle. + $angles[++$idx] = array($split,$ne); + $adjcolors[$idx] = $colors[$i % $numcolors]; + $adjexplode[$idx] = $explode; + } + } + $a += $da; + $a = $this->NormAngle($a); + } + + // Total number of slices + $n = count($angles); + + for($i=0; $i<$n; ++$i) { + list($dbgs,$dbge) = $angles[$i]; + } + + // + // Step 2. Find start index (first pie that starts in upper left quadrant) + // + $minval = $angles[0][0]; + $min = 0; + for( $i=0; $i<$n; ++$i ) { + if( $angles[$i][0] < $minval ) { + $minval = $angles[$i][0]; + $min = $i; + } + } + $j = $min; + $cnt = 0; + while( $angles[$j][1] <= 90 ) { + $j++; + if( $j>=$n) { + $j=0; + } + if( $cnt > $n ) { + JpGraphError::RaiseL(14005); +//("Pie3D Internal error (#1). Trying to wrap twice when looking for start index"); + } + ++$cnt; + } + $start = $j; + + // + // Step 3. Print slices in z-order + // + $cnt = 0; + + // First stroke all the slices between 90 and 270 (left half circle) + // counterclockwise + + while( $angles[$j][0] < 270 && $aaoption !== 2 ) { + + list($x,$y) = $adjexplode[$j]; + + $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1], + $z,$adjcolors[$j],$shadow); + + $last = array($x,$y,$j); + + $j++; + if( $j >= $n ) $j=0; + if( $cnt > $n ) { + JpGraphError::RaiseL(14006); +//("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking."); + } + ++$cnt; + } + + $slice_left = $n-$cnt; + $j=$start-1; + if($j<0) $j=$n-1; + $cnt = 0; + + // The stroke all slices from 90 to -90 (right half circle) + // clockwise + while( $cnt < $slice_left && $aaoption !== 2 ) { + + list($x,$y) = $adjexplode[$j]; + + $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1], + $z,$adjcolors[$j],$shadow); + $j--; + if( $cnt > $n ) { + JpGraphError::RaiseL(14006); +//("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking."); + } + if($j<0) $j=$n-1; + $cnt++; + } + + // Now do a special thing. Stroke the last slice on the left + // halfcircle one more time. This is needed in the case where + // the slice close to 270 have been exploded. In that case the + // part of the slice close to the center of the pie might be + // slightly nagged. + if( $aaoption !== 2 ) + $this->Pie3DSlice($img,$last[0],$last[1],$d,$h,$angles[$last[2]][0], + $angles[$last[2]][1],$z,$adjcolors[$last[2]],$shadow); + + + if( $aaoption !== 1 ) { + // Now print possible labels and add csim + $this->value->ApplyFont($img); + $margin = $img->GetFontHeight()/2 + $this->value->margin ; + for($i=0; $i < count($data); ++$i ) { + $la = $labeldata[$i][0]; + $x = $labeldata[$i][1] + cos($la*M_PI/180)*($d+$margin)*$this->ilabelposadj; + $y = $labeldata[$i][2] - sin($la*M_PI/180)*($h+$margin)*$this->ilabelposadj; + if( $this->ilabelposadj >= 1.0 ) { + if( $la > 180 && $la < 360 ) $y += $z; + } + if( $this->labeltype == 0 ) { + if( $sum > 0 ) + $l = 100*$data[$i]/$sum; + else + $l = 0; + } + elseif( $this->labeltype == 1 ) { + $l = $data[$i]; + } + else { + $l = $this->adjusted_data[$i]; + } + if( isset($this->labels[$i]) && is_string($this->labels[$i]) ) + $l=sprintf($this->labels[$i],$l); + + $this->StrokeLabels($l,$img,$labeldata[$i][0]*M_PI/180,$x,$y,$z); + + $this->Add3DSliceToCSIM($i,$labeldata[$i][1],$labeldata[$i][2],$h*2,$d*2,$z, + $originalangles[$i][0],$originalangles[$i][1]); + } + } + + // + // Finally add potential lines in pie + // + + if( $edgecolor=="" || $aaoption !== 0 ) return; + + $accsum = 0; + $a = $startangle; + $a = $this->NormAngle($a); + + $a *= M_PI/180.0; + + $idx=0; + $img->PushColor($edgecolor); + $img->SetLineWeight($edgeweight); + + $fulledge = true; + for($i=0; $i < count($data) && $fulledge; ++$i ) { + if( empty($this->explode_radius[$i]) ) + $this->explode_radius[$i]=0; + if( $this->explode_radius[$i] > 0 ) { + $fulledge = false; + } + } + + + for($i=0; $i < count($data); ++$i, ++$idx ) { + + $da = $data[$i]/$sum * 2*M_PI; + $this->StrokeFullSliceFrame($img,$xc,$yc,$a,$a+$da,$d,$h,$z,$edgecolor, + $this->explode_radius[$i],$fulledge); + $a += $da; + } + $img->PopColor(); + } + + function StrokeFullSliceFrame($img,$xc,$yc,$sa,$ea,$w,$h,$z,$edgecolor,$exploderadius,$fulledge) { + $step = 0.02; + + if( $exploderadius > 0 ) { + $la = ($sa+$ea)/2; + $xc += $exploderadius*cos($la); + $yc -= $exploderadius*sin($la) * ($h/$w) ; + + } + + $p = array($xc,$yc,$xc+$w*cos($sa),$yc-$h*sin($sa)); + + for($a=$sa; $a < $ea; $a += $step ) { + $p[] = $xc + $w*cos($a); + $p[] = $yc - $h*sin($a); + } + + $p[] = $xc+$w*cos($ea); + $p[] = $yc-$h*sin($ea); + $p[] = $xc; + $p[] = $yc; + + $img->SetColor($edgecolor); + $img->Polygon($p); + + // Unfortunately we can't really draw the full edge around the whole of + // of the slice if any of the slices are exploded. The reason is that + // this algorithm is to simply. There are cases where the edges will + // "overwrite" other slices when they have been exploded. + // Doing the full, proper 3D hidden lines stiff is actually quite + // tricky. So for exploded pies we only draw the top edge. Not perfect + // but the "real" solution is much more complicated. + if( $fulledge && !( $sa > 0 && $sa < M_PI && $ea < M_PI) ) { + + if($sa < M_PI && $ea > M_PI) + $sa = M_PI; + + if($sa < 2*M_PI && (($ea >= 2*M_PI) || ($ea > 0 && $ea < $sa ) ) ) + $ea = 2*M_PI; + + if( $sa >= M_PI && $ea <= 2*M_PI ) { + $p = array($xc + $w*cos($sa),$yc - $h*sin($sa), + $xc + $w*cos($sa),$z + $yc - $h*sin($sa)); + + for($a=$sa+$step; $a < $ea; $a += $step ) { + $p[] = $xc + $w*cos($a); + $p[] = $z + $yc - $h*sin($a); + } + $p[] = $xc + $w*cos($ea); + $p[] = $z + $yc - $h*sin($ea); + $p[] = $xc + $w*cos($ea); + $p[] = $yc - $h*sin($ea); + $img->SetColor($edgecolor); + $img->Polygon($p); + } + } + } + + function Stroke($img,$aaoption=0) { + $n = count($this->data); + + // If user hasn't set the colors use the theme array + if( $this->setslicecolors==null ) { + $colors = array_keys($img->rgb->rgb_table); + sort($colors); + $idx_a=$this->themearr[$this->theme]; + $ca = array(); + $m = count($idx_a); + for($i=0; $i < $m; ++$i) + $ca[$i] = $colors[$idx_a[$i]]; + $ca = array_reverse(array_slice($ca,0,$n)); + } + else { + $ca = $this->setslicecolors; + } + + + if( $this->posx <= 1 && $this->posx > 0 ) + $xc = round($this->posx*$img->width); + else + $xc = $this->posx ; + + if( $this->posy <= 1 && $this->posy > 0 ) + $yc = round($this->posy*$img->height); + else + $yc = $this->posy ; + + if( $this->radius <= 1 ) { + $width = floor($this->radius*min($img->width,$img->height)); + // Make sure that the pie doesn't overflow the image border + // The 0.9 factor is simply an extra margin to leave some space + // between the pie an the border of the image. + $width = min($width,min($xc*0.9,($yc*90/$this->angle-$width/4)*0.9)); + } + else { + $width = $this->radius * ($aaoption === 1 ? 2 : 1 ) ; + } + + // Add a sanity check for width + if( $width < 1 ) { + JpGraphError::RaiseL(14007);//("Width for 3D Pie is 0. Specify a size > 0"); + } + + // Establish a thickness. By default the thickness is a fifth of the + // pie slice width (=pie radius) but since the perspective depends + // on the inclination angle we use some heuristics to make the edge + // slightly thicker the less the angle. + + // Has user specified an absolute thickness? In that case use + // that instead + + if( $this->iThickness ) { + $thick = $this->iThickness; + $thick *= ($aaoption === 1 ? 2 : 1 ); + } + else + $thick = $width/12; + $a = $this->angle; + if( $a <= 30 ) $thick *= 1.6; + elseif( $a <= 40 ) $thick *= 1.4; + elseif( $a <= 50 ) $thick *= 1.2; + elseif( $a <= 60 ) $thick *= 1.0; + elseif( $a <= 70 ) $thick *= 0.8; + elseif( $a <= 80 ) $thick *= 0.7; + else $thick *= 0.6; + + $thick = floor($thick); + + if( $this->explode_all ) + for($i=0; $i < $n; ++$i) + $this->explode_radius[$i]=$this->explode_r; + + $this->Pie3D($aaoption,$img,$this->data, $ca, $xc, $yc, $width, $this->angle, + $thick, 0.65, $this->startangle, $this->edgecolor, $this->edgeweight); + + // Adjust title position + if( $aaoption != 1 ) { + $this->title->SetPos($xc,$yc-$this->title->GetFontHeight($img)-$width/2-$this->title->margin, "center","bottom"); + $this->title->Stroke($img); + } + } + +//--------------- +// PRIVATE METHODS + + // Position the labels of each slice + function StrokeLabels($label,$img,$a,$xp,$yp,$z) { + $this->value->halign="left"; + $this->value->valign="top"; + + // Position the axis title. + // dx, dy is the offset from the top left corner of the bounding box that sorrounds the text + // that intersects with the extension of the corresponding axis. The code looks a little + // bit messy but this is really the only way of having a reasonable position of the + // axis titles. + $this->value->ApplyFont($img); + $h=$img->GetTextHeight($label); + // For numeric values the format of the display value + // must be taken into account + if( is_numeric($label) ) { + if( $label >= 0 ) + $w=$img->GetTextWidth(sprintf($this->value->format,$label)); + else + $w=$img->GetTextWidth(sprintf($this->value->negformat,$label)); + } + else + $w=$img->GetTextWidth($label); + while( $a > 2*M_PI ) $a -= 2*M_PI; + if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0; + if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI; + if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1; + if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI); + + if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI; + if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI); + if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1; + if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI); + if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0; + + $x = round($xp-$dx*$w); + $y = round($yp-$dy*$h); + + + // Mark anchor point for debugging + /* + $img->SetColor('red'); + $img->Line($xp-10,$yp,$xp+10,$yp); + $img->Line($xp,$yp-10,$xp,$yp+10); + */ + $oldmargin = $this->value->margin; + $this->value->margin=0; + $this->value->Stroke($img,$label,$x,$y); + $this->value->margin=$oldmargin; + + } +} // Class + +/* EOF */ +?> diff --git a/src/classes/jpgraph/jpgraph_plotband.php b/src/classes/jpgraph/jpgraph_plotband.php new file mode 100644 index 0000000..5c6fc29 --- /dev/null +++ b/src/classes/jpgraph/jpgraph_plotband.php @@ -0,0 +1,635 @@ +x=$aX; + $this->y=$aY; + $this->w=$aWidth; + $this->h=$aHeight; + $this->xe=$aX+$aWidth-1; + $this->ye=$aY+$aHeight-1; + } +} + +//===================================================================== +// Class RectPattern +// Base class for pattern hierarchi that is used to display patterned +// bands on the graph. Any subclass that doesn't override Stroke() +// must at least implement method DoPattern($aImg) which is responsible +// for drawing the pattern onto the graph. +//===================================================================== +class RectPattern { + protected $color; + protected $weight; + protected $rect=null; + protected $doframe=true; + protected $linespacing; // Line spacing in pixels + protected $iBackgroundColor=-1; // Default is no background fill + + function RectPattern($aColor,$aWeight=1) { + $this->color = $aColor; + $this->weight = $aWeight; + } + + function SetBackground($aBackgroundColor) { + $this->iBackgroundColor=$aBackgroundColor; + } + + function SetPos($aRect) { + $this->rect = $aRect; + } + + function ShowFrame($aShow=true) { + $this->doframe=$aShow; + } + + function SetDensity($aDens) { + if( $aDens < 1 || $aDens > 100 ) + JpGraphError::RaiseL(16001,$aDens); +//(" Desity for pattern must be between 1 and 100. (You tried $aDens)"); + // 1% corresponds to linespacing=50 + // 100 % corresponds to linespacing 1 + $this->linespacing = floor(((100-$aDens)/100.0)*50)+1; + + } + + function Stroke($aImg) { + if( $this->rect == null ) + JpGraphError::RaiseL(16002); +//(" No positions specified for pattern."); + + if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) { + $aImg->SetColor($this->iBackgroundColor); + $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye); + } + + $aImg->SetColor($this->color); + $aImg->SetLineWeight($this->weight); + + // Virtual function implemented by subclass + $this->DoPattern($aImg); + + // Frame around the pattern area + if( $this->doframe ) + $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye); + } + +} + + +//===================================================================== +// Class RectPatternSolid +// Implements a solid band +//===================================================================== +class RectPatternSolid extends RectPattern { + + function RectPatternSolid($aColor="black",$aWeight=1) { + parent::RectPattern($aColor,$aWeight); + } + + function DoPattern($aImg) { + $aImg->SetColor($this->color); + $aImg->FilledRectangle($this->rect->x,$this->rect->y, + $this->rect->xe,$this->rect->ye); + } +} + +//===================================================================== +// Class RectPatternHor +// Implements horizontal line pattern +//===================================================================== +class RectPatternHor extends RectPattern { + + function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) { + parent::RectPattern($aColor,$aWeight); + $this->linespacing = $aLineSpacing; + } + + function DoPattern($aImg) { + $x0 = $this->rect->x; + $x1 = $this->rect->xe; + $y = $this->rect->y; + while( $y < $this->rect->ye ) { + $aImg->Line($x0,$y,$x1,$y); + $y += $this->linespacing; + } + } +} + +//===================================================================== +// Class RectPatternVert +// Implements vertical line pattern +//===================================================================== +class RectPatternVert extends RectPattern { + + function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) { + parent::RectPattern($aColor,$aWeight); + $this->linespacing = $aLineSpacing; + } + + //-------------------- + // Private methods + // + function DoPattern($aImg) { + $x = $this->rect->x; + $y0 = $this->rect->y; + $y1 = $this->rect->ye; + while( $x < $this->rect->xe ) { + $aImg->Line($x,$y0,$x,$y1); + $x += $this->linespacing; + } + } +} + + +//===================================================================== +// Class RectPatternRDiag +// Implements right diagonal pattern +//===================================================================== +class RectPatternRDiag extends RectPattern { + + function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) { + parent::RectPattern($aColor,$aWeight); + $this->linespacing = $aLineSpacing; + } + + function DoPattern($aImg) { + // -------------------- + // | / / / / /| + // |/ / / / / | + // | / / / / | + // -------------------- + $xe = $this->rect->xe; + $ye = $this->rect->ye; + $x0 = $this->rect->x + round($this->linespacing/2); + $y0 = $this->rect->y; + $x1 = $this->rect->x; + $y1 = $this->rect->y + round($this->linespacing/2); + + while($x0<=$xe && $y1<=$ye) { + $aImg->Line($x0,$y0,$x1,$y1); + $x0 += $this->linespacing; + $y1 += $this->linespacing; + } + + if( $xe-$x1 > $ye-$y0 ) { + // Width larger than height + $x1 = $this->rect->x + ($y1-$ye); + $y1 = $ye; + $y0 = $this->rect->y; + while( $x0 <= $xe ) { + $aImg->Line($x0,$y0,$x1,$y1); + $x0 += $this->linespacing; + $x1 += $this->linespacing; + } + + $y0=$this->rect->y + ($x0-$xe); + $x0=$xe; + } + else { + // Height larger than width + $diff = $x0-$xe; + $y0 = $diff+$this->rect->y; + $x0 = $xe; + $x1 = $this->rect->x; + while( $y1 <= $ye ) { + $aImg->Line($x0,$y0,$x1,$y1); + $y1 += $this->linespacing; + $y0 += $this->linespacing; + } + + $diff = $y1-$ye; + $y1 = $ye; + $x1 = $diff + $this->rect->x; + } + + while( $y0 <= $ye ) { + $aImg->Line($x0,$y0,$x1,$y1); + $y0 += $this->linespacing; + $x1 += $this->linespacing; + } + } +} + +//===================================================================== +// Class RectPatternLDiag +// Implements left diagonal pattern +//===================================================================== +class RectPatternLDiag extends RectPattern { + + function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) { + $this->linespacing = $aLineSpacing; + parent::RectPattern($aColor,$aWeight); + } + + function DoPattern($aImg) { + // -------------------- + // |\ \ \ \ \ | + // | \ \ \ \ \| + // | \ \ \ \ | + // |------------------| + $xe = $this->rect->xe; + $ye = $this->rect->ye; + $x0 = $this->rect->x + round($this->linespacing/2); + $y0 = $this->rect->ye; + $x1 = $this->rect->x; + $y1 = $this->rect->ye - round($this->linespacing/2); + + while($x0<=$xe && $y1>=$this->rect->y) { + $aImg->Line($x0,$y0,$x1,$y1); + $x0 += $this->linespacing; + $y1 -= $this->linespacing; + } + if( $xe-$x1 > $ye-$this->rect->y ) { + // Width larger than height + $x1 = $this->rect->x + ($this->rect->y-$y1); + $y0=$ye; $y1=$this->rect->y; + while( $x0 <= $xe ) { + $aImg->Line($x0,$y0,$x1,$y1); + $x0 += $this->linespacing; + $x1 += $this->linespacing; + } + + $y0=$this->rect->ye - ($x0-$xe); + $x0=$xe; + } + else { + // Height larger than width + $diff = $x0-$xe; + $y0 = $ye-$diff; + $x0 = $xe; + while( $y1 >= $this->rect->y ) { + $aImg->Line($x0,$y0,$x1,$y1); + $y0 -= $this->linespacing; + $y1 -= $this->linespacing; + } + $diff = $this->rect->y - $y1; + $x1 = $this->rect->x + $diff; + $y1 = $this->rect->y; + } + while( $y0 >= $this->rect->y ) { + $aImg->Line($x0,$y0,$x1,$y1); + $y0 -= $this->linespacing; + $x1 += $this->linespacing; + } + } +} + +//===================================================================== +// Class RectPattern3DPlane +// Implements "3D" plane pattern +//===================================================================== +class RectPattern3DPlane extends RectPattern { + private $alpha=50; // Parameter that specifies the distance + // to "simulated" horizon in pixel from the + // top of the band. Specifies how fast the lines + // converge. + + function RectPattern3DPlane($aColor="black",$aWeight=1) { + parent::RectPattern($aColor,$aWeight); + $this->SetDensity(10); // Slightly larger default + } + + function SetHorizon($aHorizon) { + $this->alpha=$aHorizon; + } + + function DoPattern($aImg) { + // "Fake" a nice 3D grid-effect. + $x0 = $this->rect->x + $this->rect->w/2; + $y0 = $this->rect->y; + $x1 = $x0; + $y1 = $this->rect->ye; + $x0_right = $x0; + $x1_right = $x1; + + // BTW "apa" means monkey in Swedish but is really a shortform for + // "alpha+a" which was the labels I used on paper when I derived the + // geometric to get the 3D perspective right. + // $apa is the height of the bounding rectangle plus the distance to the + // artifical horizon (alpha) + $apa = $this->rect->h + $this->alpha; + + // Three cases and three loops + // 1) The endpoint of the line ends on the bottom line + // 2) The endpoint ends on the side + // 3) Horizontal lines + + // Endpoint falls on bottom line + $middle=$this->rect->x + $this->rect->w/2; + $dist=$this->linespacing; + $factor=$this->alpha /($apa); + while($x1>$this->rect->x) { + $aImg->Line($x0,$y0,$x1,$y1); + $aImg->Line($x0_right,$y0,$x1_right,$y1); + $x1 = $middle - $dist; + $x0 = $middle - $dist * $factor; + $x1_right = $middle + $dist; + $x0_right = $middle + $dist * $factor; + $dist += $this->linespacing; + } + + // Endpoint falls on sides + $dist -= $this->linespacing; + $d=$this->rect->w/2; + $c = $apa - $d*$apa/$dist; + while( $x0>$this->rect->x ) { + $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c); + $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c); + $dist += $this->linespacing; + $x0 = $middle - $dist * $factor; + $x1 = $middle - $dist; + $x0_right = $middle + $dist * $factor; + $c = $apa - $d*$apa/$dist; + } + + // Horizontal lines + // They need some serious consideration since they are a function + // of perspective depth (alpha) and density (linespacing) + $x0=$this->rect->x; + $x1=$this->rect->xe; + $y=$this->rect->ye; + + // The first line is drawn directly. Makes the loop below slightly + // more readable. + $aImg->Line($x0,$y,$x1,$y); + $hls = $this->linespacing; + + // A correction factor for vertical "brick" line spacing to account for + // a) the difference in number of pixels hor vs vert + // b) visual apperance to make the first layer of "bricks" look more + // square. + $vls = $this->linespacing*0.6; + + $ds = $hls*($apa-$vls)/$apa; + // Get the slope for the "perspective line" going from bottom right + // corner to top left corner of the "first" brick. + + // Uncomment the following lines if you want to get a visual understanding + // of what this helpline does. BTW this mimics the way you would get the + // perspective right when drawing on paper. + /* + $x0 = $middle; + $y0 = $this->rect->ye; + $len=floor(($this->rect->ye-$this->rect->y)/$vls); + $x1 = $middle+round($len*$ds); + $y1 = $this->rect->ye-$len*$vls; + $aImg->PushColor("red"); + $aImg->Line($x0,$y0,$x1,$y1); + $aImg->PopColor(); + */ + + $y -= $vls; + $k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds)); + $dist = $hls; + while( $y>$this->rect->y ) { + $aImg->Line($this->rect->x,$y,$this->rect->xe,$y); + $adj = $k*$dist/(1+$dist*$k/$apa); + if( $adj < 2 ) $adj=1; + $y = $this->rect->ye - round($adj); + $dist += $hls; + } + } +} + +//===================================================================== +// Class RectPatternCross +// Vert/Hor crosses +//===================================================================== +class RectPatternCross extends RectPattern { + private $vert=null; + private $hor=null; + function RectPatternCross($aColor="black",$aWeight=1) { + parent::RectPattern($aColor,$aWeight); + $this->vert = new RectPatternVert($aColor,$aWeight); + $this->hor = new RectPatternHor($aColor,$aWeight); + } + + function SetOrder($aDepth) { + $this->vert->SetOrder($aDepth); + $this->hor->SetOrder($aDepth); + } + + function SetPos($aRect) { + parent::SetPos($aRect); + $this->vert->SetPos($aRect); + $this->hor->SetPos($aRect); + } + + function SetDensity($aDens) { + $this->vert->SetDensity($aDens); + $this->hor->SetDensity($aDens); + } + + function DoPattern($aImg) { + $this->vert->DoPattern($aImg); + $this->hor->DoPattern($aImg); + } +} + +//===================================================================== +// Class RectPatternDiagCross +// Vert/Hor crosses +//===================================================================== + +class RectPatternDiagCross extends RectPattern { + private $left=null; + private $right=null; + function RectPatternDiagCross($aColor="black",$aWeight=1) { + parent::RectPattern($aColor,$aWeight); + $this->right = new RectPatternRDiag($aColor,$aWeight); + $this->left = new RectPatternLDiag($aColor,$aWeight); + } + + function SetOrder($aDepth) { + $this->left->SetOrder($aDepth); + $this->right->SetOrder($aDepth); + } + + function SetPos($aRect) { + parent::SetPos($aRect); + $this->left->SetPos($aRect); + $this->right->SetPos($aRect); + } + + function SetDensity($aDens) { + $this->left->SetDensity($aDens); + $this->right->SetDensity($aDens); + } + + function DoPattern($aImg) { + $this->left->DoPattern($aImg); + $this->right->DoPattern($aImg); + } + +} + +//===================================================================== +// Class RectPatternFactory +// Factory class for rectangular pattern +//===================================================================== +class RectPatternFactory { + function RectPatternFactory() { + // Empty + } + function Create($aPattern,$aColor,$aWeight=1) { + switch($aPattern) { + case BAND_RDIAG: + $obj = new RectPatternRDiag($aColor,$aWeight); + break; + case BAND_LDIAG: + $obj = new RectPatternLDiag($aColor,$aWeight); + break; + case BAND_SOLID: + $obj = new RectPatternSolid($aColor,$aWeight); + break; + case BAND_VLINE: + $obj = new RectPatternVert($aColor,$aWeight); + break; + case BAND_HLINE: + $obj = new RectPatternHor($aColor,$aWeight); + break; + case BAND_3DPLANE: + $obj = new RectPattern3DPlane($aColor,$aWeight); + break; + case BAND_HVCROSS: + $obj = new RectPatternCross($aColor,$aWeight); + break; + case BAND_DIAGCROSS: + $obj = new RectPatternDiagCross($aColor,$aWeight); + break; + default: + JpGraphError::RaiseL(16003,$aPattern); +//(" Unknown pattern specification ($aPattern)"); + } + return $obj; + } +} + + +//===================================================================== +// Class PlotBand +// Factory class which is used by the client. +// It is responsible for factoring the corresponding pattern +// concrete class. +//===================================================================== +class PlotBand { + public $depth; // Determine if band should be over or under the plots + private $prect=null; + private $dir, $min, $max; + + function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) { + $f = new RectPatternFactory(); + $this->prect = $f->Create($aPattern,$aColor,$aWeight); + if( is_numeric($aMin) && is_numeric($aMax) && ($aMin > $aMax) ) + JpGraphError::RaiseL(16004); +//('Min value for plotband is larger than specified max value. Please correct.'); + $this->dir = $aDir; + $this->min = $aMin; + $this->max = $aMax; + $this->depth=$aDepth; + } + + // Set position. aRect contains absolute image coordinates + function SetPos($aRect) { + assert( $this->prect != null ) ; + $this->prect->SetPos($aRect); + } + + function ShowFrame($aFlag=true) { + $this->prect->ShowFrame($aFlag); + } + + // Set z-order. In front of pplot or in the back + function SetOrder($aDepth) { + $this->depth=$aDepth; + } + + function SetDensity($aDens) { + $this->prect->SetDensity($aDens); + } + + function GetDir() { + return $this->dir; + } + + function GetMin() { + return $this->min; + } + + function GetMax() { + return $this->max; + } + + function PreStrokeAdjust($aGraph) { + // Nothing to do + } + + // Display band + function Stroke($aImg,$aXScale,$aYScale) { + assert( $this->prect != null ) ; + if( $this->dir == HORIZONTAL ) { + if( $this->min === 'min' ) $this->min = $aYScale->GetMinVal(); + if( $this->max === 'max' ) $this->max = $aYScale->GetMaxVal(); + + // Only draw the bar if it actually appears in the range + if ($this->min < $aYScale->GetMaxVal() && $this->max > $aYScale->GetMinVal()) { + + // Trucate to limit of axis + $this->min = max($this->min, $aYScale->GetMinVal()); + $this->max = min($this->max, $aYScale->GetMaxVal()); + + $x=$aXScale->scale_abs[0]; + $y=$aYScale->Translate($this->max); + $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1; + $height=abs($y-$aYScale->Translate($this->min))+1; + $this->prect->SetPos(new Rectangle($x,$y,$width,$height)); + $this->prect->Stroke($aImg); + } + } + else { // VERTICAL + if( $this->min === 'min' ) $this->min = $aXScale->GetMinVal(); + if( $this->max === 'max' ) $this->max = $aXScale->GetMaxVal(); + + // Only draw the bar if it actually appears in the range + if ($this->min < $aXScale->GetMaxVal() && $this->max > $aXScale->GetMinVal()) { + + // Trucate to limit of axis + $this->min = max($this->min, $aXScale->GetMinVal()); + $this->max = min($this->max, $aXScale->GetMaxVal()); + + $y=$aYScale->scale_abs[1]; + $x=$aXScale->Translate($this->min); + $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]); + $width=abs($x-$aXScale->Translate($this->max)); + $this->prect->SetPos(new Rectangle($x,$y,$width,$height)); + $this->prect->Stroke($aImg); + } + } + } +} + + +?> diff --git a/src/classes/jpgraph/jpgraph_plotmark.inc.php b/src/classes/jpgraph/jpgraph_plotmark.inc.php new file mode 100644 index 0000000..c8a381a --- /dev/null +++ b/src/classes/jpgraph/jpgraph_plotmark.inc.php @@ -0,0 +1,497 @@ +title = new Text(); + $this->title->Hide(); + $this->csimareas = ''; + $this->type=-1; + } +//--------------- +// PUBLIC METHODS + function SetType($aType,$aFileName='',$aScale=1.0) { + $this->type = $aType; + if( $aType == MARK_IMG && $aFileName=='' ) { + JpGraphError::RaiseL(23003);//('A filename must be specified if you set the mark type to MARK_IMG.'); + } + $this->iFileName = $aFileName; + $this->iScale = $aScale; + } + + function SetCallback($aFunc) { + $this->iFormatCallback = $aFunc; + } + + function SetCallbackYX($aFunc) { + $this->iFormatCallback2 = $aFunc; + } + + function GetType() { + return $this->type; + } + + function SetColor($aColor) { + $this->color=$aColor; + } + + function SetFillColor($aFillColor) { + $this->fill_color = $aFillColor; + } + + function SetWeight($aWeight) { + $this->weight = $aWeight; + } + + // Synonym for SetWidth() + function SetSize($aWidth) { + $this->width=$aWidth; + } + + function SetWidth($aWidth) { + $this->width=$aWidth; + } + + function SetDefaultWidth() { + switch( $this->type ) { + case MARK_CIRCLE: + case MARK_FILLEDCIRCLE: + $this->width=4; + break; + default: + $this->width=7; + } + } + + function GetWidth() { + return $this->width; + } + + function Hide($aHide=true) { + $this->show = !$aHide; + } + + function Show($aShow=true) { + $this->show = $aShow; + } + + function SetCSIMAltVal($aY,$aX='') { + $this->yvalue=$aY; + $this->xvalue=$aX; + } + + function SetCSIMTarget($aTarget,$aWinTarget='') { + $this->csimtarget=$aTarget; + $this->csimwintarget=$aWinTarget; + } + + function SetCSIMAlt($aAlt) { + $this->csimalt=$aAlt; + } + + function GetCSIMAreas(){ + return $this->csimareas; + } + + function AddCSIMPoly($aPts) { + $coords = round($aPts[0]).", ".round($aPts[1]); + $n = count($aPts)/2; + for( $i=1; $i < $n; ++$i){ + $coords .= ", ".round($aPts[2*$i]).", ".round($aPts[2*$i+1]); + } + $this->csimareas=""; + if( !empty($this->csimtarget) ) { + $this->csimareas .= "csimtarget)."\""; + + if( !empty($this->csimwintarget) ) { + $this->csimareas .= " target=\"".$this->csimwintarget."\" "; + } + + if( !empty($this->csimalt) ) { + $tmp=sprintf($this->csimalt,$this->yvalue,$this->xvalue); + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\""; + } + $this->csimareas .= " />\n"; + } + } + + function AddCSIMCircle($x,$y,$r) { + $x = round($x); $y=round($y); $r=round($r); + $this->csimareas=""; + if( !empty($this->csimtarget) ) { + $this->csimareas .= "csimtarget)."\""; + + if( !empty($this->csimwintarget) ) { + $this->csimareas .= " target=\"".$this->csimwintarget."\" "; + } + + if( !empty($this->csimalt) ) { + $tmp=sprintf($this->csimalt,$this->yvalue,$this->xvalue); + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + } + + function Stroke($img,$x,$y) { + if( !$this->show ) return; + + if( $this->iFormatCallback != '' || $this->iFormatCallback2 != '' ) { + + if( $this->iFormatCallback != '' ) { + $f = $this->iFormatCallback; + list($width,$color,$fcolor) = call_user_func($f,$this->yvalue); + $filename = $this->iFileName; + $imgscale = $this->iScale; + } + else { + $f = $this->iFormatCallback2; + list($width,$color,$fcolor,$filename,$imgscale) = call_user_func($f,$this->yvalue,$this->xvalue); + if( $filename=="" ) $filename = $this->iFileName; + if( $imgscale=="" ) $imgscale = $this->iScale; + } + + if( $width=="" ) $width = $this->width; + if( $color=="" ) $color = $this->color; + if( $fcolor=="" ) $fcolor = $this->fill_color; + + } + else { + $fcolor = $this->fill_color; + $color = $this->color; + $width = $this->width; + $filename = $this->iFileName; + $imgscale = $this->iScale; + } + + if( $this->type == MARK_IMG || + ($this->type >= MARK_FLAG1 && $this->type <= MARK_FLAG4 ) || + $this->type >= MARK_IMG_PUSHPIN ) { + + // Note: For the builtin images we use the "filename" parameter + // to denote the color + $anchor_x = 0.5; + $anchor_y = 0.5; + switch( $this->type ) { + case MARK_FLAG1: + case MARK_FLAG2: + case MARK_FLAG3: + case MARK_FLAG4: + $this->markimg = FlagCache::GetFlagImgByName($this->type-MARK_FLAG1+1,$filename); + break; + + case MARK_IMG : + // Load an image and use that as a marker + // Small optimization, if we have already read an image don't + // waste time reading it again. + if( $this->markimg == '' || !($this->oldfilename === $filename) ) { + $this->markimg = Graph::LoadBkgImage('',$filename); + $this->oldfilename = $filename ; + } + break; + + case MARK_IMG_PUSHPIN: + case MARK_IMG_SPUSHPIN: + case MARK_IMG_LPUSHPIN: + if( $this->imgdata_pushpins == null ) { + require_once 'imgdata_pushpins.inc.php'; + $this->imgdata_pushpins = new ImgData_PushPins(); + } + $this->markimg = $this->imgdata_pushpins->GetImg($this->type,$filename); + list($anchor_x,$anchor_y) = $this->imgdata_pushpins->GetAnchor(); + break; + + case MARK_IMG_SQUARE: + if( $this->imgdata_squares == null ) { + require_once 'imgdata_squares.inc.php'; + $this->imgdata_squares = new ImgData_Squares(); + } + $this->markimg = $this->imgdata_squares->GetImg($this->type,$filename); + list($anchor_x,$anchor_y) = $this->imgdata_squares->GetAnchor(); + break; + + case MARK_IMG_STAR: + if( $this->imgdata_stars == null ) { + require_once 'imgdata_stars.inc.php'; + $this->imgdata_stars = new ImgData_Stars(); + } + $this->markimg = $this->imgdata_stars->GetImg($this->type,$filename); + list($anchor_x,$anchor_y) = $this->imgdata_stars->GetAnchor(); + break; + + case MARK_IMG_BEVEL: + if( $this->imgdata_bevels == null ) { + require_once 'imgdata_bevels.inc.php'; + $this->imgdata_bevels = new ImgData_Bevels(); + } + $this->markimg = $this->imgdata_bevels->GetImg($this->type,$filename); + list($anchor_x,$anchor_y) = $this->imgdata_bevels->GetAnchor(); + break; + + case MARK_IMG_DIAMOND: + if( $this->imgdata_diamonds == null ) { + require_once 'imgdata_diamonds.inc.php'; + $this->imgdata_diamonds = new ImgData_Diamonds(); + } + $this->markimg = $this->imgdata_diamonds->GetImg($this->type,$filename); + list($anchor_x,$anchor_y) = $this->imgdata_diamonds->GetAnchor(); + break; + + case MARK_IMG_BALL: + case MARK_IMG_SBALL: + case MARK_IMG_MBALL: + case MARK_IMG_LBALL: + if( $this->imgdata_balls == null ) { + require_once 'imgdata_balls.inc.php'; + $this->imgdata_balls = new ImgData_Balls(); + } + $this->markimg = $this->imgdata_balls->GetImg($this->type,$filename); + list($anchor_x,$anchor_y) = $this->imgdata_balls->GetAnchor(); + break; + } + + $w = $img->GetWidth($this->markimg); + $h = $img->GetHeight($this->markimg); + + $dw = round($imgscale * $w ); + $dh = round($imgscale * $h ); + + // Do potential rotation + list($x,$y) = $img->Rotate($x,$y); + + $dx = round($x-$dw*$anchor_x); + $dy = round($y-$dh*$anchor_y); + + $this->width = max($dx,$dy); + + $img->Copy($this->markimg,$dx,$dy,0,0,$dw,$dh,$w,$h); + if( !empty($this->csimtarget) ) { + $this->csimareas = "csimtarget)."\""; + + if( !empty($this->csimwintarget) ) { + $this->csimareas .= " target=\"".$this->csimwintarget."\" "; + } + + if( !empty($this->csimalt) ) { + $tmp=sprintf($this->csimalt,$this->yvalue,$this->xvalue); + $this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; + } + $this->csimareas .= " />\n"; + } + + // Stroke title + $this->title->Align("center","top"); + $this->title->Stroke($img,$x,$y+round($dh/2)); + return; + } + + $weight = $this->weight; + $dx=round($width/2,0); + $dy=round($width/2,0); + $pts=0; + + switch( $this->type ) { + case MARK_SQUARE: + $c[]=$x-$dx;$c[]=$y-$dy; + $c[]=$x+$dx;$c[]=$y-$dy; + $c[]=$x+$dx;$c[]=$y+$dy; + $c[]=$x-$dx;$c[]=$y+$dy; + $c[]=$x-$dx;$c[]=$y-$dy; + $pts=5; + break; + case MARK_UTRIANGLE: + ++$dx;++$dy; + $c[]=$x-$dx;$c[]=$y+0.87*$dy; // tan(60)/2*$dx + $c[]=$x;$c[]=$y-0.87*$dy; + $c[]=$x+$dx;$c[]=$y+0.87*$dy; + $c[]=$x-$dx;$c[]=$y+0.87*$dy; // tan(60)/2*$dx + $pts=4; + break; + case MARK_DTRIANGLE: + ++$dx;++$dy; + $c[]=$x;$c[]=$y+0.87*$dy; // tan(60)/2*$dx + $c[]=$x-$dx;$c[]=$y-0.87*$dy; + $c[]=$x+$dx;$c[]=$y-0.87*$dy; + $c[]=$x;$c[]=$y+0.87*$dy; // tan(60)/2*$dx + $pts=4; + break; + case MARK_DIAMOND: + $c[]=$x;$c[]=$y+$dy; + $c[]=$x-$dx;$c[]=$y; + $c[]=$x;$c[]=$y-$dy; + $c[]=$x+$dx;$c[]=$y; + $c[]=$x;$c[]=$y+$dy; + $pts=5; + break; + case MARK_LEFTTRIANGLE: + $c[]=$x;$c[]=$y; + $c[]=$x;$c[]=$y+2*$dy; + $c[]=$x+$dx*2;$c[]=$y; + $c[]=$x;$c[]=$y; + $pts=4; + break; + case MARK_RIGHTTRIANGLE: + $c[]=$x-$dx*2;$c[]=$y; + $c[]=$x;$c[]=$y+2*$dy; + $c[]=$x;$c[]=$y; + $c[]=$x-$dx*2;$c[]=$y; + $pts=4; + break; + case MARK_FLASH: + $dy *= 2; + $c[]=$x+$dx/2; $c[]=$y-$dy; + $c[]=$x-$dx+$dx/2; $c[]=$y+$dy*0.7-$dy; + $c[]=$x+$dx/2; $c[]=$y+$dy*1.3-$dy; + $c[]=$x-$dx+$dx/2; $c[]=$y+2*$dy-$dy; + $img->SetLineWeight($weight); + $img->SetColor($color); + $img->Polygon($c); + $img->SetLineWeight(1); + $this->AddCSIMPoly($c); + break; + } + + if( $pts>0 ) { + $this->AddCSIMPoly($c); + $img->SetLineWeight($weight); + $img->SetColor($fcolor); + $img->FilledPolygon($c); + $img->SetColor($color); + $img->Polygon($c); + $img->SetLineWeight(1); + } + elseif( $this->type==MARK_CIRCLE ) { + $img->SetColor($color); + $img->Circle($x,$y,$width); + $this->AddCSIMCircle($x,$y,$width); + } + elseif( $this->type==MARK_FILLEDCIRCLE ) { + $img->SetColor($fcolor); + $img->FilledCircle($x,$y,$width); + $img->SetColor($color); + $img->Circle($x,$y,$width); + $this->AddCSIMCircle($x,$y,$width); + } + elseif( $this->type==MARK_CROSS ) { + // Oversize by a pixel to match the X + $img->SetColor($color); + $img->SetLineWeight($weight); + $img->Line($x,$y+$dy+1,$x,$y-$dy-1); + $img->Line($x-$dx-1,$y,$x+$dx+1,$y); + $this->AddCSIMCircle($x,$y,$dx); + } + elseif( $this->type==MARK_X ) { + $img->SetColor($color); + $img->SetLineWeight($weight); + $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy); + $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy); + $this->AddCSIMCircle($x,$y,$dx+$dy); + } + elseif( $this->type==MARK_STAR ) { + $img->SetColor($color); + $img->SetLineWeight($weight); + $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy); + $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy); + // Oversize by a pixel to match the X + $img->Line($x,$y+$dy+1,$x,$y-$dy-1); + $img->Line($x-$dx-1,$y,$x+$dx+1,$y); + $this->AddCSIMCircle($x,$y,$dx+$dy); + } + + // Stroke title + $this->title->Align("center","center"); + $this->title->Stroke($img,$x,$y); + } +} // Class + + + +//======================================================================== +// CLASS ImgData +// Description: Base class for all image data classes that contains the +// real image data. +//======================================================================== +class ImgData { + protected $name = ''; // Each subclass gives a name + protected $an = array(); // Data array names + protected $colors = array(); // Available colors + protected $index = array(); // Index for colors + protected $maxidx = 0 ; // Max color index + protected $anchor_x=0.5, $anchor_y=0.5 ; // Where is the center of the image + // Create a GD image from the data and return a GD handle + function GetImg($aMark,$aIdx) { + $n = $this->an[$aMark]; + if( is_string($aIdx) ) { + if( !in_array($aIdx,$this->colors) ) { + JpGraphError::RaiseL(23001,$this->name,$aIdx);//('This marker "'.($this->name).'" does not exist in color: '.$aIdx); + } + $idx = $this->index[$aIdx]; + } + elseif( !is_integer($aIdx) || + (is_integer($aIdx) && $aIdx > $this->maxidx ) ) { + JpGraphError::RaiseL(23002,$this->name);//('Mark color index too large for marker "'.($this->name).'"'); + } + else + $idx = $aIdx ; + return Image::CreateFromString(base64_decode($this->{$n}[$idx][1])); + } + function GetAnchor() { + return array($this->anchor_x,$this->anchor_y); + } +} + + +// Keep a global flag cache to reduce memory usage +$_gFlagCache=array( + 1 => null, + 2 => null, + 3 => null, + 4 => null, +); +// Only supposed to b called as statics +class FlagCache { + static function GetFlagImgByName($aSize,$aName) { + global $_gFlagCache; + require_once('jpgraph_flags.php'); + if( $_gFlagCache[$aSize] === null ) { + $_gFlagCache[$aSize] = new FlagImages($aSize); + } + $f = $_gFlagCache[$aSize]; + $idx = $f->GetIdxByName($aName,$aFullName); + return $f->GetImgByIdx($idx); + } +} + +?> \ No newline at end of file diff --git a/src/classes/jpgraph/jpgraph_ttf.inc.php b/src/classes/jpgraph/jpgraph_ttf.inc.php new file mode 100644 index 0000000..02050ee --- /dev/null +++ b/src/classes/jpgraph/jpgraph_ttf.inc.php @@ -0,0 +1,339 @@ +g2312 == null ) { + include_once 'jpgraph_gb2312.php' ; + $this->g2312 = new GB2312toUTF8(); + } + return $this->g2312->gb2utf8($aTxt); + } + elseif( $aFF === FF_CHINESE ) { + if( !function_exists('iconv') ) { + JpGraphError::RaiseL(25006); +//('Usage of FF_CHINESE (FF_BIG5) font family requires that your PHP setup has the iconv() function. By default this is not compiled into PHP (needs the "--width-iconv" when configured).'); + } + return iconv('BIG5','UTF-8',$aTxt); + } + elseif( ASSUME_EUCJP_ENCODING && + ($aFF == FF_MINCHO || $aFF == FF_GOTHIC || $aFF == FF_PMINCHO || $aFF == FF_PGOTHIC) ) { + if( !function_exists('mb_convert_encoding') ) { + JpGraphError::RaiseL(25127); + } + return mb_convert_encoding($aTxt, 'UTF-8','EUC-JP'); + } + elseif( $aFF == FF_DAVID || $aFF == FF_MIRIAM || $aFF == FF_AHRON ) { + return LanguageConv::heb_iso2uni($aTxt); + } + else + return $aTxt; + } + + // Translate iso encoding to unicode + public static function iso2uni ($isoline){ + $uniline=''; + for ($i=0; $i < strlen($isoline); $i++){ + $thischar=substr($isoline,$i,1); + $charcode=ord($thischar); + $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar; + } + return $uniline; + } + + // Translate greek iso encoding to unicode + public static function gr_iso2uni ($isoline) { + $uniline=''; + for ($i=0; $i < strlen($isoline); $i++) { + $thischar=substr($isoline,$i,1); + $charcode=ord($thischar); + $uniline.=($charcode>179 && $charcode!=183 && $charcode!=187 && $charcode!=189) ? "&#" . (900+($charcode-180)). ";" : $thischar; + } + return $uniline; + } + + // Translate greek win encoding to unicode + public static function gr_win2uni ($winline) { + $uniline=''; + for ($i=0; $i < strlen($winline); $i++) { + $thischar=substr($winline,$i,1); + $charcode=ord($thischar); + if ($charcode==161 || $charcode==162) { + $uniline.="&#" . (740+$charcode). ";"; + } + else { + $uniline.=(($charcode>183 && $charcode!=187 && $charcode!=189) || $charcode==180) ? "&#" . (900+($charcode-180)). ";" : $thischar; + } + } + return $uniline; + } + + public static function heb_iso2uni($isoline) { + $isoline = hebrev($isoline); + $o = ''; + + $n = strlen($isoline); + for($i=0; $i < $n; $i++) { + $c=ord( substr($isoline,$i,1) ); + $o .= ($c > 223) && ($c < 251) ? '&#'.(1264+$c).';' : chr($c); + } + return utf8_encode($o); + } +} + +//============================================================= +// CLASS TTF +// Description: Handle TTF font names and mapping and loading of +// font files +//============================================================= +class TTF { + private $font_files,$style_names; + +//--------------- +// CONSTRUCTOR + function TTF() { + + // String names for font styles to be used in error messages + $this->style_names=array(FS_NORMAL =>'normal', + FS_BOLD =>'bold', + FS_ITALIC =>'italic', + FS_BOLDITALIC =>'bolditalic'); + + // File names for available fonts + $this->font_files=array( + FF_COURIER => array(FS_NORMAL =>'cour.ttf', + FS_BOLD =>'courbd.ttf', + FS_ITALIC =>'couri.ttf', + FS_BOLDITALIC =>'courbi.ttf' ), + FF_GEORGIA => array(FS_NORMAL =>'georgia.ttf', + FS_BOLD =>'georgiab.ttf', + FS_ITALIC =>'georgiai.ttf', + FS_BOLDITALIC =>'' ), + FF_TREBUCHE =>array(FS_NORMAL =>'trebuc.ttf', + FS_BOLD =>'trebucbd.ttf', + FS_ITALIC =>'trebucit.ttf', + FS_BOLDITALIC =>'trebucbi.ttf' ), + FF_VERDANA => array(FS_NORMAL =>'verdana.ttf', + FS_BOLD =>'verdanab.ttf', + FS_ITALIC =>'verdanai.ttf', + FS_BOLDITALIC =>'' ), + FF_TIMES => array(FS_NORMAL =>'times.ttf', + FS_BOLD =>'timesbd.ttf', + FS_ITALIC =>'timesi.ttf', + FS_BOLDITALIC =>'timesbi.ttf' ), + FF_COMIC => array(FS_NORMAL =>'comic.ttf', + FS_BOLD =>'comicbd.ttf', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_ARIAL => array(FS_NORMAL =>'arial.ttf', + FS_BOLD =>'arialbd.ttf', + FS_ITALIC =>'ariali.ttf', + FS_BOLDITALIC =>'arialbi.ttf' ) , + FF_VERA => array(FS_NORMAL =>'Vera.ttf', + FS_BOLD =>'VeraBd.ttf', + FS_ITALIC =>'VeraIt.ttf', + FS_BOLDITALIC =>'VeraBI.ttf' ), + FF_VERAMONO => array(FS_NORMAL =>'VeraMono.ttf', + FS_BOLD =>'VeraMoBd.ttf', + FS_ITALIC =>'VeraMoIt.ttf', + FS_BOLDITALIC =>'VeraMoBI.ttf' ), + FF_VERASERIF=> array(FS_NORMAL =>'VeraSe.ttf', + FS_BOLD =>'VeraSeBd.ttf', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ) , + + /* Chinese fonts */ + FF_SIMSUN => array(FS_NORMAL =>'simsun.ttc', + FS_BOLD =>'simhei.ttf', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_CHINESE => array(FS_NORMAL =>CHINESE_TTF_FONT, + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + + /* Japanese fonts */ + FF_MINCHO => array(FS_NORMAL =>MINCHO_TTF_FONT, + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_PMINCHO => array(FS_NORMAL =>PMINCHO_TTF_FONT, + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_GOTHIC => array(FS_NORMAL =>GOTHIC_TTF_FONT, + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_PGOTHIC => array(FS_NORMAL =>PGOTHIC_TTF_FONT, + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_MINCHO => array(FS_NORMAL =>PMINCHO_TTF_FONT, + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + + /* Hebrew fonts */ + FF_DAVID => array(FS_NORMAL =>'DAVIDNEW.TTF', + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + + FF_MIRIAM => array(FS_NORMAL =>'MRIAMY.TTF', + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + + FF_AHRON => array(FS_NORMAL =>'ahronbd.ttf', + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + + /* Misc fonts */ + FF_DIGITAL => array(FS_NORMAL =>'DIGIRU__.TTF', + FS_BOLD =>'Digirtu_.ttf', + FS_ITALIC =>'Digir___.ttf', + FS_BOLDITALIC =>'DIGIRT__.TTF' ), + FF_SPEEDO => array(FS_NORMAL =>'Speedo.ttf', + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_COMPUTER => array(FS_NORMAL =>'COMPUTER.TTF', + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + FF_CALCULATOR => array(FS_NORMAL =>'Triad_xs.ttf', + FS_BOLD =>'', + FS_ITALIC =>'', + FS_BOLDITALIC =>'' ), + ); + } + +//--------------- +// PUBLIC METHODS + // Create the TTF file from the font specification + function File($family,$style=FS_NORMAL) { + $fam = @$this->font_files[$family]; + if( !$fam ) { + JpGraphError::RaiseL(25046,$family);//("Specified TTF font family (id=$family) is unknown or does not exist. Please note that TTF fonts are not distributed with JpGraph for copyright reasons. You can find the MS TTF WEB-fonts (arial, courier etc) for download at http://corefonts.sourceforge.net/"); + } + $f = @$fam[$style]; + + if( $f==='' ) + JpGraphError::RaiseL(25047,$this->style_names[$style],$this->font_files[$family][FS_NORMAL]);//('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.'); + if( !$f ) { + JpGraphError::RaiseL(25048,$fam);//("Unknown font style specification [$fam]."); + } + + if ($family >= FF_MINCHO && $family <= FF_PGOTHIC) { + $f = MBTTF_DIR.$f; + } else { + $f = TTF_DIR.$f; + } + + if( file_exists($f) === false || is_readable($f) === false ) { + JpGraphError::RaiseL(25049,$f);//("Font file \"$f\" is not readable or does not exist."); + } + return $f; + } +} // Class + + + +?> diff --git a/src/classes/jpgraph/lang/de.inc.php b/src/classes/jpgraph/lang/de.inc.php new file mode 100644 index 0000000..04725f7 --- /dev/null +++ b/src/classes/jpgraph/lang/de.inc.php @@ -0,0 +1,500 @@ +,) +$_jpg_messages = array( + +/* +** Headers wurden bereits gesendet - Fehler. Dies wird als HTML formatiert, weil es direkt als text zurueckgesendet wird +*/ +10 => array('
    JpGraph Fehler: +HTTP header wurden bereits gesendet.
    Fehler in der Datei %s in der Zeile %d.
    Erklärung:
    HTTP header wurden bereits zum Browser gesendet, wobei die Daten als Text gekennzeichnet wurden, bevor die Bibliothek die Chance hatte, seinen Bild-HTTP-Header zum Browser zu schicken. Dies verhindert, dass die Bibliothek Bilddaten zum Browser schicken kann (weil sie vom Browser als Text interpretiert würden und daher nur Mist dargestellt würde).

    Wahrscheinlich steht Text im Skript bevor Graph::Stroke() aufgerufen wird. Wenn dieser Text zum Browser gesendet wird, nimmt dieser an, dass die gesamten Daten aus Text bestehen. Such nach irgendwelchem Text, auch nach Leerzeichen und Zeilenumbrüchen, die eventuell bereits zum Browser gesendet wurden.

    Zum Beispiel ist ein oft auftretender Fehler, eine Leerzeile am Anfang der Datei oder vor Graph::Stroke() zu lassen."<?php".

    ',2), + +/* +** Setup Fehler +*/ +11 => array('Es wurde kein Pfad für CACHE_DIR angegeben. Bitte gib einen Pfad CACHE_DIR in der Datei jpg-config.inc an.',0), +12 => array('Es wurde kein Pfad für TTF_DIR angegeben und der Pfad kann nicht automatisch ermittelt werden. Bitte gib den Pfad in der Datei jpg-config.inc an.',0), +13 => array('The installed PHP version (%s) is not compatible with this release of the library. The library requires at least PHP version %s',2), + +/* +** jpgraph_bar +*/ + +2001 => array('Die Anzahl der Farben ist nicht gleich der Anzahl der Vorlagen in BarPlot::SetPattern().',0), +2002 => array('Unbekannte Vorlage im Aufruf von BarPlot::SetPattern().',0), +2003 => array('Anzahl der X- und Y-Koordinaten sind nicht identisch. Anzahl der X-Koordinaten: %d; Anzahl der Y-Koordinaten: %d.',2), +2004 => array('Alle Werte für ein Balkendiagramm (barplot) müssen numerisch sein. Du hast den Wert nr [%d] == %s angegeben.',2), +2005 => array('Du hast einen leeren Vektor für die Schattierungsfarben im Balkendiagramm (barplot) angegeben.',0), +2006 => array('Unbekannte Position für die Werte der Balken: %s.',1), +2007 => array('Kann GroupBarPlot nicht aus einem leeren Vektor erzeugen.',0), +2008 => array('GroupBarPlot Element nbr %d wurde nicht definiert oder ist leer.',0), +2009 => array('Eins der Objekte, das an GroupBar weitergegeben wurde ist kein Balkendiagramm (BarPlot). Versichere Dich, dass Du den GroupBarPlot aus einem Vektor von Balkendiagrammen (barplot) oder AccBarPlot-Objekten erzeugst. (Class = %s)',1), +2010 => array('Kann AccBarPlot nicht aus einem leeren Vektor erzeugen.',0), +2011 => array('AccBarPlot-Element nbr %d wurde nicht definiert oder ist leer.',1), +2012 => array('Eins der Objekte, das an AccBar weitergegeben wurde ist kein Balkendiagramm (barplot). Versichere Dich, dass Du den AccBar-Plot aus einem Vektor von Balkendiagrammen (barplot) erzeugst. (Class=%s)',1), +2013 => array('Du hast einen leeren Vektor für die Schattierungsfarben im Balkendiagramm (barplot) angegeben.',0), +2014 => array('Die Anzahl der Datenpunkte jeder Datenreihe in AccBarPlot muss gleich sein.',0), + + +/* +** jpgraph_date +*/ + +3001 => array('Es ist nur möglich, entweder SetDateAlign() oder SetTimeAlign() zu benutzen, nicht beides!',0), + +/* +** jpgraph_error +*/ + +4002 => array('Fehler bei den Eingabedaten von LineErrorPlot. Die Anzahl der Datenpunkte mus ein Mehrfaches von drei sein!',0), + +/* +** jpgraph_flags +*/ + +5001 => array('Unbekannte Flaggen-Größe (%d).',1), +5002 => array('Der Flaggen-Index %s existiert nicht.',1), +5003 => array('Es wurde eine ungültige Ordnungszahl (%d) für den Flaggen-Index angegeben.',1), +5004 => array('Der Landesname %s hat kein korrespondierendes Flaggenbild. Die Flagge mag existieren, abr eventuell unter einem anderen Namen, z.B. versuche "united states" statt "usa".',1), + + +/* +** jpgraph_gantt +*/ + +6001 => array('Interner Fehler. Die Höhe für ActivityTitles ist < 0.',0), +6002 => array('Es dürfen keine negativen Werte für die Gantt-Diagramm-Dimensionen angegeben werden. Verwende 0, wenn die Dimensionen automatisch ermittelt werden sollen.',0), +6003 => array('Ungültiges Format für den Bedingungs-Parameter bei Index=%d in CreateSimple(). Der Parameter muss bei index 0 starten und Vektoren in der Form (Row,Constrain-To,Constrain-Type) enthalten.',1), +6004 => array('Ungültiges Format für den Fortschritts-Parameter bei Index=%d in CreateSimple(). Der Parameter muss bei Index 0 starten und Vektoren in der Form (Row,Progress) enthalten.',1), +6005 => array('SetScale() ist nicht sinnvoll bei Gantt-Diagrammen.',0), +6006 => array('Das Gantt-Diagramm kann nicht automatisch skaliert werden. Es existieren keine Aktivitäten mit Termin. [GetBarMinMax() start >= n]',0), +6007 => array('Plausibiltätsprüfung für die automatische Gantt-Diagramm-Größe schlug fehl. Entweder die Breite (=%d) oder die Höhe (=%d) ist größer als MAX_GANTTIMG_SIZE. Dies kann möglicherweise durch einen falschen Wert bei einer Aktivität hervorgerufen worden sein.',2), +6008 => array('Du hast eine Bedingung angegeben von Reihe=%d bis Reihe=%d, die keine Aktivität hat.',2), +6009 => array('Unbekannter Bedingungstyp von Reihe=%d bis Reihe=%d',2), +6010 => array('Ungültiger Icon-Index für das eingebaute Gantt-Icon [%d]',1), +6011 => array('Argument für IconImage muss entweder ein String oder ein Integer sein.',0), +6012 => array('Unbekannter Typ bei der Gantt-Objekt-Title-Definition.',0), +6015 => array('Ungültige vertikale Position %d',1), +6016 => array('Der eingegebene Datums-String (%s) für eine Gantt-Aktivität kann nicht interpretiert werden. Versichere Dich, dass es ein gültiger Datumsstring ist, z.B. 2005-04-23 13:30',1), +6017 => array('Unbekannter Datumstyp in GanttScale (%s).',1), +6018 => array('Intervall für Minuten muss ein gerader Teiler einer Stunde sein, z.B. 1,5,10,12,15,20,30, etc. Du hast ein Intervall von %d Minuten angegeben.',1), +6019 => array('Die vorhandene Breite (%d) für die Minuten ist zu klein, um angezeigt zu werden. Bitte benutze die automatische Größenermittlung oder vergrößere die Breite des Diagramms.',1), +6020 => array('Das Intervall für die Stunden muss ein gerader Teiler eines Tages sein, z.B. 0:30, 1:00, 1:30, 4:00, etc. Du hast ein Intervall von %d eingegeben.',1), +6021 => array('Unbekanntes Format für die Woche.',0), +6022 => array('Die Gantt-Skala wurde nicht eingegeben.',0), +6023 => array('Wenn Du sowohl Stunden als auch Minuten anzeigen lassen willst, muss das Stunden-Interval gleich 1 sein (anderenfalls ist es nicht sinnvoll, Minuten anzeigen zu lassen).',0), +6024 => array('Das CSIM-Ziel muss als String angegeben werden. Der Start des Ziels ist: %d',1), +6025 => array('Der CSIM-Alt-Text muss als String angegeben werden. Der Beginn des Alt-Textes ist: %d',1), +6027 => array('Der Fortschrittswert muss im Bereich [0, 1] liegen.',0), +6028 => array('Die eingegebene Höhe (%d) für GanttBar ist nicht im zulässigen Bereich.',1), +6029 => array('Der Offset für die vertikale Linie muss im Bereich [0,1] sein.',0), +6030 => array('Unbekannte Pfeilrichtung für eine Verbindung.',0), +6031 => array('Unbekannter Pfeiltyp für eine Verbindung.',0), +6032 => array('Interner Fehler: Unbekannter Pfadtyp (=%d) für eine Verbindung.',1), + +/* +** jpgraph_gradient +*/ + +7001 => array('Unbekannter Gradiententyp (=%d).',1), + +/* +** jpgraph_iconplot +*/ + +8001 => array('Der Mix-Wert für das Icon muss zwischen 0 und 100 sein.',0), +8002 => array('Die Ankerposition für Icons muss entweder "top", "bottom", "left", "right" oder "center" sein.',0), +8003 => array('Es ist nicht möglich, gleichzeitig ein Bild und eine Landesflagge für dasselbe Icon zu definieren',0), +8004 => array('Wenn Du Landesflaggen benutzen willst, musst Du die Datei "jpgraph_flags.php" hinzufügen (per include).',0), + +/* +** jpgraph_imgtrans +*/ + +9001 => array('Der Wert für die Bildtransformation ist außerhalb des zulässigen Bereichs. Der verschwindende Punkt am Horizont muss als Wert zwischen 0 und 1 angegeben werden.',0), + +/* +** jpgraph_lineplot +*/ + +10001 => array('Die Methode LinePlot::SetFilled() sollte nicht mehr benutzt werden. Benutze lieber SetFillColor()',0), +10002 => array('Der Plot ist zu kompliziert für FastLineStroke. Benutze lieber den StandardStroke()',0), +10003 => array('Each plot in an accumulated lineplot must have the same number of data points.',0), +/* +** jpgraph_log +*/ + +11001 => array('Deine Daten enthalten nicht-numerische Werte.',0), +11002 => array('Negative Werte können nicht für logarithmische Achsen verwendet werden.',0), +11003 => array('Deine Daten enthalten nicht-numerische Werte.',0), +11004 => array('Skalierungsfehler für die logarithmische Achse. Es gibt ein Problem mit den Daten der Achse. Der größte Wert muss größer sein als Null. Es ist mathematisch nicht möglich, einen Wert gleich Null in der Skala zu haben.',0), +11005 => array('Das Tick-Intervall für die logarithmische Achse ist nicht definiert. Lösche jeden Aufruf von SetTextLabelStart() oder SetTextTickInterval() bei der logarithmischen Achse.',0), + +/* +** jpgraph_mgraph +*/ + +12001 => array("Du benutzt GD 2.x und versuchst ein Nicht-Truecolor-Bild als Hintergrundbild zu benutzen. Um Hintergrundbilder mit GD 2.x zu benutzen, ist es notwendig Truecolor zu aktivieren, indem die USE_TRUECOLOR-Konstante auf TRUE gesetzt wird. Wegen eines Bugs in GD 2.0.1 ist die Qualität der Truetype-Schriften sehr schlecht, wenn man Truetype-Schriften mit Truecolor-Bildern verwendet.",0), +12002 => array('Ungültiger Dateiname für MGraph::SetBackgroundImage() : %s. Die Datei muss eine gültige Dateierweiterung haben (jpg,gif,png), wenn die automatische Typerkennung verwendet wird.',1), +12003 => array('Unbekannte Dateierweiterung (%s) in MGraph::SetBackgroundImage() für Dateiname: %s',2), +12004 => array('Das Bildformat des Hintergrundbildes (%s) wird von Deiner System-Konfiguration nicht unterstützt. ',1), +12005 => array('Das Hintergrundbild kann nicht gelesen werden: %s',1), +12006 => array('Es wurden ungültige Größen für Breite oder Höhe beim Erstellen des Bildes angegeben, (Breite=%d, Höhe=%d)',2), +12007 => array('Das Argument für MGraph::Add() ist nicht gültig für GD.',0), +12008 => array('Deine PHP- (und GD-lib-) Installation scheint keine bekannten Bildformate zu unterstützen.',0), +12009 => array('Deine PHP-Installation unterstützt das gewählte Bildformat nicht: %s',1), +12010 => array('Es konnte kein Bild als Datei %s erzeugt werden. Überprüfe, ob Du die entsprechenden Schreibrechte im aktuellen Verzeichnis hast.',1), +12011 => array('Es konnte kein Truecolor-Bild erzeugt werden. Überprüfe, ob Du wirklich die GD2-Bibliothek installiert hast.',0), +12012 => array('Es konnte kein Bild erzeugt werden. Überprüfe, ob Du wirklich die GD2-Bibliothek installiert hast.',0), + +/* +** jpgraph_pie3d +*/ + +14001 => array('Pie3D::ShowBorder(). Missbilligte Funktion. Benutze Pie3D::SetEdge(), um die Ecken der Tortenstücke zu kontrollieren.',0), +14002 => array('PiePlot3D::SetAngle() 3D-Torten-Projektionswinkel muss zwischen 5 und 85 Grad sein.',0), +14003 => array('Interne Festlegung schlug fehl. Pie3D::Pie3DSlice',0), +14004 => array('Tortenstück-Startwinkel muss zwischen 0 und 360 Grad sein.',0), +14005 => array('Pie3D Interner Fehler: Versuch, zweimal zu umhüllen bei der Suche nach dem Startindex.',0,), +14006 => array('Pie3D Interner Fehler: Z-Sortier-Algorithmus für 3D-Tortendiagramme funktioniert nicht einwandfrei (2). Versuch, zweimal zu umhüllen beim Erstellen des Bildes.',0), +14007 => array('Die Breite für das 3D-Tortendiagramm ist 0. Gib eine Breite > 0 an.',0), + +/* +** jpgraph_pie +*/ + +15001 => array('PiePLot::SetTheme() Unbekannter Stil: %s',1), +15002 => array('Argument für PiePlot::ExplodeSlice() muss ein Integer-Wert sein',0), +15003 => array('Argument für PiePlot::Explode() muss ein Vektor mit Integer-Werten sein.',0), +15004 => array('Tortenstück-Startwinkel muss zwischen 0 und 360 Grad sein.',0), +15005 => array('PiePlot::SetFont() sollte nicht mehr verwendet werden. Benutze stattdessen PiePlot->value->SetFont().',0), +15006 => array('PiePlot::SetSize() Radius für Tortendiagramm muss entweder als Bruch [0, 0.5] der Bildgröße oder als Absoluwert in Pixel im Bereich [10, 1000] angegeben werden.',0), +15007 => array('PiePlot::SetFontColor() sollte nicht mehr verwendet werden. Benutze stattdessen PiePlot->value->SetColor()..',0), +15008 => array('PiePlot::SetLabelType() der Typ für Tortendiagramme muss entweder 0 or 1 sein (nicht %d).',1), +15009 => array('Ungültiges Tortendiagramm. Die Summe aller Daten ist Null.',0), +15010 => array('Die Summe aller Daten ist Null.',0), +15011 => array('Um Bildtransformationen benutzen zu können, muss die Datei jpgraph_imgtrans.php eingefügt werden (per include).',0), + +/* +** jpgraph_plotband +*/ + +16001 => array('Die Dichte für das Pattern muss zwischen 1 und 100 sein. (Du hast %f eingegeben)',1), +16002 => array('Es wurde keine Position für das Pattern angegeben.',0), +16003 => array('Unbekannte Pattern-Definition (%d)',0), +16004 => array('Der Mindeswert für das PlotBand ist größer als der Maximalwert. Bitte korrigiere dies!',0), + + +/* +** jpgraph_polar +*/ + +17001 => array('PolarPlots müssen eine gerade Anzahl von Datenpunkten haben. Jeder Datenpunkt ist ein Tupel (Winkel, Radius).',0), +17002 => array('Unbekannte Ausrichtung für X-Achsen-Titel. (%s)',1), +//17003 => array('Set90AndMargin() wird für PolarGraph nicht unterstützt.',0), +17004 => array('Unbekannter Achsentyp für PolarGraph. Er muss entweder \'lin\' oder \'log\' sein.',0), + +/* +** jpgraph_radar +*/ + +18001 => array('ClientSideImageMaps werden für RadarPlots nicht unterstützt.',0), +18002 => array('RadarGraph::SupressTickMarks() sollte nicht mehr verwendet werden. Benutze stattdessen HideTickMarks().',0), +18003 => array('Ungültiger Achsentyp für RadarPlot (%s). Er muss entweder \'lin\' oder \'log\' sein.',1), +18004 => array('Die RadarPlot-Größe muss zwischen 0.1 und 1 sein. (Dein Wert=%f)',1), +18005 => array('RadarPlot: nicht unterstützte Tick-Dichte: %d',1), +18006 => array('Minimum Daten %f (RadarPlots sollten nur verwendet werden, wenn alle Datenpunkte einen Wert > 0 haben).',1), +18007 => array('Die Anzahl der Titel entspricht nicht der Anzahl der Datenpunkte.',0), +18008 => array('Jeder RadarPlot muss die gleiche Anzahl von Datenpunkten haben.',0), + +/* +** jpgraph_regstat +*/ + +19001 => array('Spline: Anzahl der X- und Y-Koordinaten muss gleich sein.',0), +19002 => array('Ungültige Dateneingabe für Spline. Zwei oder mehr aufeinanderfolgende X-Werte sind identisch. Jeder eigegebene X-Wert muss unterschiedlich sein, weil vom mathematischen Standpunkt ein Eins-zu-Eins-Mapping vorliegen muss, d.h. jeder X-Wert korrespondiert mit exakt einem Y-Wert.',0), +19003 => array('Bezier: Anzahl der X- und Y-Koordinaten muss gleich sein.',0), + +/* +** jpgraph_scatter +*/ + +20001 => array('Fieldplots müssen die gleiche Anzahl von X und Y Datenpunkten haben.',0), +20002 => array('Bei Fieldplots muss ein Winkel für jeden X und Y Datenpunkt angegeben werden.',0), +20003 => array('Scatterplots müssen die gleiche Anzahl von X- und Y-Datenpunkten haben.',0), + +/* +** jpgraph_stock +*/ + +21001 => array('Die Anzahl der Datenwerte für Stock-Charts müssen ein Mehrfaches von %d Datenpunkten sein.',1), + +/* +** jpgraph_plotmark +*/ + +23001 => array('Der Marker "%s" existiert nicht in der Farbe: %d',2), +23002 => array('Der Farb-Index ist zu hoch für den Marker "%s"',1), +23003 => array('Ein Dateiname muss angegeben werden, wenn Du den Marker-Typ auf MARK_IMG setzt.',0), + +/* +** jpgraph_utils +*/ + +24001 => array('FuncGenerator : Keine Funktion definiert. ',0), +24002 => array('FuncGenerator : Syntax-Fehler in der Funktionsdefinition ',0), +24003 => array('DateScaleUtils: Unknown tick type specified in call to GetTicks()',0), + +/* +** jpgraph +*/ + +25001 => array('Diese PHP-Installation ist nicht mit der GD-Bibliothek kompiliert. Bitte kompiliere PHP mit GD-Unterstützung neu, damit JpGraph funktioniert. (Weder die Funktion imagetypes() noch imagecreatefromstring() existiert!)',0), +25002 => array('Diese PHP-Installation scheint nicht die benötigte GD-Bibliothek zu unterstützen. Bitte schau in der PHP-Dokumentation nach, wie man die GD-Bibliothek installiert und aktiviert.',0), +25003 => array('Genereller PHP Fehler : Bei %s:%d : %s',3), +25004 => array('Genereller PHP Fehler : %s ',1), +25005 => array('PHP_SELF, die PHP-Global-Variable kann nicht ermittelt werden. PHP kann nicht von der Kommandozeile gestartet werden, wenn der Cache oder die Bilddateien automatisch benannt werden sollen.',0), +25006 => array('Die Benutzung der FF_CHINESE (FF_BIG5) Schriftfamilie benötigt die iconv() Funktion in Deiner PHP-Konfiguration. Dies wird nicht defaultmäßig in PHP kompiliert (benötigt "--width-iconv" bei der Konfiguration).',0), +25007 => array('Du versuchst das lokale (%s) zu verwenden, was von Deiner PHP-Installation nicht unterstützt wird. Hinweis: Benutze \'\', um das defaultmäßige Lokale für diese geographische Region festzulegen.',1), +25008 => array('Die Bild-Breite und Höhe in Graph::Graph() müssen numerisch sein',0), +25009 => array('Die Skalierung der Achsen muss angegeben werden mit Graph::SetScale()',0), + +25010 => array('Graph::Add() Du hast versucht, einen leeren Plot zum Graph hinzuzufügen.',0), +25011 => array('Graph::AddY2() Du hast versucht, einen leeren Plot zum Graph hinzuzufügen.',0), +25012 => array('Graph::AddYN() Du hast versucht, einen leeren Plot zum Graph hinzuzufügen.',0), +25013 => array('Es können nur Standard-Plots zu multiplen Y-Achsen hinzugefügt werden',0), +25014 => array('Graph::AddText() Du hast versucht, einen leeren Text zum Graph hinzuzufügen.',0), +25015 => array('Graph::AddLine() Du hast versucht, eine leere Linie zum Graph hinzuzufügen.',0), +25016 => array('Graph::AddBand() Du hast versucht, ein leeres Band zum Graph hinzuzufügen.',0), +25017 => array('Du benutzt GD 2.x und versuchst, ein Hintergrundbild in einem Truecolor-Bild zu verwenden. Um Hintergrundbilder mit GD 2.x zu verwenden, ist es notwendig, Truecolor zu aktivieren, indem die USE_TRUECOLOR-Konstante auf TRUE gesetzt wird. Wegen eines Bugs in GD 2.0.1 ist die Qualität der Schrift sehr schlecht, wenn Truetype-Schrift in Truecolor-Bildern verwendet werden.',0), +25018 => array('Falscher Dateiname für Graph::SetBackgroundImage() : "%s" muss eine gültige Dateinamenerweiterung (jpg,gif,png) haben, wenn die automatische Dateityperkennung verwenndet werden soll.',1), +25019 => array('Unbekannte Dateinamenerweiterung (%s) in Graph::SetBackgroundImage() für Dateiname: "%s"',2), + +25020 => array('Graph::SetScale(): Dar Maximalwert muss größer sein als der Mindestwert.',0), +25021 => array('Unbekannte Achsendefinition für die Y-Achse. (%s)',1), +25022 => array('Unbekannte Achsendefinition für die X-Achse. (%s)',1), +25023 => array('Nicht unterstützter Y2-Achsentyp: "%s" muss einer von (lin,log,int) sein.',1), +25024 => array('Nicht unterstützter X-Achsentyp: "%s" muss einer von (lin,log,int) sein.',1), +25025 => array('Nicht unterstützte Tick-Dichte: %d',1), +25026 => array('Nicht unterstützter Typ der nicht angegebenen Y-Achse. Du hast entweder: 1. einen Y-Achsentyp für automatisches Skalieren definiert, aber keine Plots angegeben. 2. eine Achse direkt definiert, aber vergessen, die Tick-Dichte zu festzulegen.',0), +25027 => array('Kann cached CSIM "%s" zum Lesen nicht öffnen.',1), +25028 => array('Apache/PHP hat keine Schreibrechte, in das CSIM-Cache-Verzeichnis (%s) zu schreiben. Überprüfe die Rechte.',1), +25029 => array('Kann nicht in das CSIM "%s" schreiben. Überprüfe die Schreibrechte und den freien Speicherplatz.',1), + +25030 => array('Fehlender Skriptname für StrokeCSIM(). Der Name des aktuellen Skriptes muss als erster Parameter von StrokeCSIM() angegeben werden.',0), +25031 => array('Der Achsentyp muss mittels Graph::SetScale() angegeben werden.',0), +25032 => array('Es existieren keine Plots für die Y-Achse nbr:%d',1), +25033 => array('',0), +25034 => array('Undefinierte X-Achse kann nicht gezeichnet werden. Es wurden keine Plots definiert.',0), +25035 => array('Du hast Clipping aktiviert. Clipping wird nur für Diagramme mit 0 oder 90 Grad Rotation unterstützt. Bitte verändere Deinen Rotationswinkel (=%d Grad) dementsprechend oder deaktiviere Clipping.',1), +25036 => array('Unbekannter Achsentyp AxisStyle() : %s',1), +25037 => array('Das Bildformat Deines Hintergrundbildes (%s) wird von Deiner System-Konfiguration nicht unterstützt. ',1), +25038 => array('Das Hintergrundbild scheint von einem anderen Typ (unterschiedliche Dateierweiterung) zu sein als der angegebene Typ. Angegebenen: %s; Datei: %s',2), +25039 => array('Hintergrundbild kann nicht gelesen werden: "%s"',1), + +25040 => array('Es ist nicht möglich, sowohl ein Hintergrundbild als auch eine Hintergrund-Landesflagge anzugeben.',0), +25041 => array('Um Landesflaggen als Hintergrund benutzen zu können, muss die Datei "jpgraph_flags.php" eingefügt werden (per include).',0), +25042 => array('Unbekanntes Hintergrundbild-Layout',0), +25043 => array('Unbekannter Titelhintergrund-Stil.',0), +25044 => array('Automatisches Skalieren kann nicht verwendet werden, weil es unmöglich ist, einen gültigen min/max Wert für die Y-Achse zu ermitteln (nur Null-Werte).',0), +25045 => array('Die Schriftfamilien FF_HANDWRT und FF_BOOK sind wegen Copyright-Problemen nicht mehr verfügbar. Diese Schriften können nicht mehr mit JpGraph verteilt werden. Bitte lade Dir Schriften von http://corefonts.sourceforge.net/ herunter.',0), +25046 => array('Angegebene TTF-Schriftfamilie (id=%d) ist unbekannt oder existiert nicht. Bitte merke Dir, dass TTF-Schriften wegen Copyright-Problemen nicht mit JpGraph mitgeliefert werden. Du findest MS-TTF-Internetschriften (arial, courier, etc.) zum Herunterladen unter http://corefonts.sourceforge.net/',1), +25047 => array('Stil %s ist nicht verfügbar für Schriftfamilie %s',2), +25048 => array('Unbekannte Schriftstildefinition [%s].',1), +25049 => array('Schriftdatei "%s" ist nicht lesbar oder existiert nicht.',1), + +25050 => array('Erstes Argument für Text::Text() muss ein String sein.',0), +25051 => array('Ungültige Richtung angegeben für Text.',0), +25052 => array('PANIK: Interner Fehler in SuperScript::Stroke(). Unbekannte vertikale Ausrichtung für Text.',0), +25053 => array('PANIK: Interner Fehler in SuperScript::Stroke(). Unbekannte horizontale Ausrichtung für Text.',0), +25054 => array('Interner Fehler: Unbekannte Grid-Achse %s',1), +25055 => array('Axis::SetTickDirection() sollte nicht mehr verwendet werden. Benutze stattdessen Axis::SetTickSide().',0), +25056 => array('SetTickLabelMargin() sollte nicht mehr verwendet werden. Benutze stattdessen Axis::SetLabelMargin().',0), +25057 => array('SetTextTicks() sollte nicht mehr verwendet werden. Benutze stattdessen SetTextTickInterval().',0), +25058 => array('TextLabelIntevall >= 1 muss angegeben werden.',0), +25059 => array('SetLabelPos() sollte nicht mehr verwendet werden. Benutze stattdessen Axis::SetLabelSide().',0), + +25060 => array('Unbekannte Ausrichtung angegeben für X-Achsentitel (%s).',1), +25061 => array('Unbekannte Ausrichtung angegeben für Y-Achsentitel (%s).',1), +25062 => array('Label unter einem Winkel werden für die Y-Achse nicht unterstützt.',0), +25063 => array('Ticks::SetPrecision() sollte nicht mehr verwendet werden. Benutze stattdessen Ticks::SetLabelFormat() (oder Ticks::SetFormatCallback()).',0), +25064 => array('Kleinere oder größere Schrittgröße ist 0. Überprüfe, ob Du fälschlicherweise SetTextTicks(0) in Deinem Skript hast. Wenn dies nicht der Fall ist, bist Du eventuell über einen Bug in JpGraph gestolpert. Bitte sende einen Report und füge den Code an, der den Fehler verursacht hat.',0), +25065 => array('Tick-Positionen müssen als array() angegeben werden',0), +25066 => array('Wenn die Tick-Positionen und -Label von Hand eingegeben werden, muss die Anzahl der Ticks und der Label gleich sein.',0), +25067 => array('Deine von Hand eingegebene Achse und Ticks sind nicht korrekt. Die Skala scheint zu klein zu sein für den Tickabstand.',0), +25068 => array('Ein Plot hat eine ungültige Achse. Dies kann beispielsweise der Fall sein, wenn Du automatisches Text-Skalieren verwendest, um ein Liniendiagramm zu zeichnen mit nur einem Datenpunkt, oder wenn die Bildfläche zu klein ist. Es kann auch der Fall sein, dass kein Datenpunkt einen numerischen Wert hat (vielleicht nur \'-\' oder \'x\').',0), +25069 => array('Grace muss größer sein als 0',0), + +25070 => array('Deine Daten enthalten nicht-numerische Werte.',0), +25071 => array('Du hast mit SetAutoMin() einen Mindestwert angegeben, der größer ist als der Maximalwert für die Achse. Dies ist nicht möglich.',0), +25072 => array('Du hast mit SetAutoMax() einen Maximalwert angegeben, der kleiner ist als der Minimalwert der Achse. Dies ist nicht möglich.',0), +25073 => array('Interner Fehler. Der Integer-Skalierungs-Algorithmus-Vergleich ist außerhalb der Grenzen (r=%f).',1), +25074 => array('Interner Fehler. Der Skalierungsbereich ist negativ (%f) [für %s Achse]. Dieses Problem könnte verursacht werden durch den Versuch, \'ungültige\' Werte in die Daten-Vektoren einzugeben (z.B. nur String- oder NULL-Werte), was beim automatischen Skalieren einen Fehler erzeugt.',2), +25075 => array('Die automatischen Ticks können nicht gesetzt werden, weil min==max.',0), +25077 => array('Einstellfaktor für die Farbe muss größer sein als 0',0), +25078 => array('Unbekannte Farbe: %s',1), +25079 => array('Unbekannte Farbdefinition: %s, Größe=%d',2), + +25080 => array('Der Alpha-Parameter für Farben muss zwischen 0.0 und 1.0 liegen.',0), +25081 => array('Das ausgewählte Grafikformat wird entweder nicht unterstützt oder ist unbekannt [%s]',1), +25082 => array('Es wurden ungültige Größen für Breite und Höhe beim Erstellen des Bildes definiert (Breite=%d, Höhe=%d).',2), +25083 => array('Es wurde eine ungültige Größe beim Kopieren des Bildes angegeben. Die Größe für das kopierte Bild wurde auf 1 Pixel oder weniger gesetzt.',0), +25084 => array('Fehler beim Erstellen eines temporären GD-Canvas. Möglicherweise liegt ein Arbeitsspeicherproblem vor.',0), +25085 => array('Ein Bild kann nicht aus dem angegebenen String erzeugt werden. Er ist entweder in einem nicht unterstützen Format oder er represäntiert ein kaputtes Bild.',0), +25086 => array('Du scheinst nur GD 1.x installiert zu haben. Um Alphablending zu aktivieren, ist GD 2.x oder höher notwendig. Bitte installiere GD 2.x oder versichere Dich, dass die Konstante USE_GD2 richtig gesetzt ist. Standardmäßig wird die installierte GD-Version automatisch erkannt. Ganz selten wird GD2 erkannt, obwohl nur GD1 installiert ist. Die Konstante USE_GD2 muss dann zu "false" gesetzt werden.',0), +25087 => array('Diese PHP-Version wurde ohne TTF-Unterstützung konfiguriert. PHP muss mit TTF-Unterstützung neu kompiliert und installiert werden.',0), +25088 => array('Die GD-Schriftunterstützung wurde falsch konfiguriert. Der Aufruf von imagefontwidth() ist fehlerhaft.',0), +25089 => array('Die GD-Schriftunterstützung wurde falsch konfiguriert. Der Aufruf von imagefontheight() ist fehlerhaft.',0), + +25090 => array('Unbekannte Richtung angegeben im Aufruf von StrokeBoxedText() [%s].',1), +25091 => array('Die interne Schrift untestützt das Schreiben von Text in einem beliebigen Winkel nicht. Benutze stattdessen TTF-Schriften.',0), +25092 => array('Es liegt entweder ein Konfigurationsproblem mit TrueType oder ein Problem beim Lesen der Schriftdatei "%s" vor. Versichere Dich, dass die Datei existiert und Leserechte und -pfad vergeben sind. (wenn \'basedir\' restriction in PHP aktiviert ist, muss die Schriftdatei im Dokumentwurzelverzeichnis abgelegt werden). Möglicherweise ist die FreeType-Bibliothek falsch installiert. Versuche, mindestens zur FreeType-Version 2.1.13 zu aktualisieren und kompiliere GD mit einem korrekten Setup neu, damit die FreeType-Bibliothek gefunden werden kann.',1), +25093 => array('Die Schriftdatei "%s" kann nicht gelesen werden beim Aufruf von Image::GetBBoxTTF. Bitte versichere Dich, dass die Schrift gesetzt wurde, bevor diese Methode aufgerufen wird, und dass die Schrift im TTF-Verzeichnis installiert ist.',1), +25094 => array('Die Textrichtung muss in einem Winkel zwischen 0 und 90 engegeben werden.',0), +25095 => array('Unbekannte Schriftfamilien-Definition. ',0), +25096 => array('Der Farbpalette können keine weiteren Farben zugewiesen werden. Dem Bild wurde bereits die größtmögliche Anzahl von Farben (%d) zugewiesen und die Palette ist voll. Verwende stattdessen ein TrueColor-Bild',0), +25097 => array('Eine Farbe wurde als leerer String im Aufruf von PushColor() angegegeben.',0), +25098 => array('Negativer Farbindex. Unpassender Aufruf von PopColor().',0), +25099 => array('Die Parameter für Helligkeit und Kontrast sind außerhalb des zulässigen Bereichs [-1,1]',0), + +25100 => array('Es liegt ein Problem mit der Farbpalette und dem GD-Setup vor. Bitte deaktiviere anti-aliasing oder verwende GD2 mit TrueColor. Wenn die GD2-Bibliothek installiert ist, versichere Dich, dass die Konstante USE_GD2 auf "true" gesetzt und TrueColor aktiviert ist.',0), +25101 => array('Ungültiges numerisches Argument für SetLineStyle(): (%d)',1), +25102 => array('Ungültiges String-Argument für SetLineStyle(): %s',1), +25103 => array('Ungültiges Argument für SetLineStyle %s',1), +25104 => array('Unbekannter Linientyp: %s',1), +25105 => array('Es wurden NULL-Daten für ein gefülltes Polygon angegeben. Sorge dafür, dass keine NULL-Daten angegeben werden.',0), +25106 => array('Image::FillToBorder : es können keine weiteren Farben zugewiesen werden.',0), +25107 => array('In Datei "%s" kann nicht geschrieben werden. Überprüfe die aktuellen Schreibrechte.',1), +25108 => array('Das Bild kann nicht gestreamt werden. Möglicherweise liegt ein Fehler im PHP/GD-Setup vor. Kompiliere PHP neu und verwende die eingebaute GD-Bibliothek, die mit PHP angeboten wird.',0), +25109 => array('Deine PHP- (und GD-lib-) Installation scheint keine bekannten Grafikformate zu unterstützen. Sorge zunächst dafür, dass GD als PHP-Modul kompiliert ist. Wenn Du außerdem JPEG-Bilder verwenden willst, musst Du die JPEG-Bibliothek installieren. Weitere Details sind in der PHP-Dokumentation zu finden.',0), + +25110 => array('Dein PHP-Installation unterstützt das gewählte Grafikformat nicht: %s',1), +25111 => array('Das gecachete Bild %s kann nicht gelöscht werden. Problem mit den Rechten?',1), +25112 => array('Das Datum der gecacheten Datei (%s) liegt in der Zukunft.',1), +25113 => array('Das gecachete Bild %s kann nicht gelöscht werden. Problem mit den Rechten?',1), +25114 => array('PHP hat nicht die erforderlichen Rechte, um in die Cache-Datei %s zu schreiben. Bitte versichere Dich, dass der Benutzer, der PHP anwendet, die entsprechenden Schreibrechte für die Datei hat, wenn Du das Cache-System in JPGraph verwenden willst.',1), +25115 => array('Berechtigung für gecachetes Bild %s kann nicht gesetzt werden. Problem mit den Rechten?',1), +25116 => array('Datei kann nicht aus dem Cache %s geöffnet werden',1), +25117 => array('Gecachetes Bild %s kann nicht zum Lesen geöffnet werden.',1), +25118 => array('Verzeichnis %s kann nicht angelegt werden. Versichere Dich, dass PHP die Schreibrechte in diesem Verzeichnis hat.',1), +25119 => array('Rechte für Datei %s können nicht gesetzt werden. Problem mit den Rechten?',1), + +25120 => array('Die Position für die Legende muss als Prozentwert im Bereich 0-1 angegeben werden.',0), +25121 => array('Eine leerer Datenvektor wurde für den Plot eingegeben. Es muss wenigstens ein Datenpunkt vorliegen.',0), +25122 => array('Stroke() muss als Subklasse der Klasse Plot definiert sein.',0), +25123 => array('Du kannst keine Text-X-Achse mit X-Koordinaten verwenden. Benutze stattdessen eine "int" oder "lin" Achse.',0), +25124 => array('Der Eingabedatenvektor mus aufeinanderfolgende Werte von 0 aufwärts beinhalten. Der angegebene Y-Vektor beginnt mit leeren Werten (NULL).',0), +25125 => array('Ungültige Richtung für statische Linie.',0), +25126 => array('Es kann kein TrueColor-Bild erzeugt werden. Überprüfe, ob die GD2-Bibliothek und PHP korrekt aufgesetzt wurden.',0), +25127 => array('The library has been configured for automatic encoding conversion of Japanese fonts. This requires that PHP has the mb_convert_encoding() function. Your PHP installation lacks this function (PHP needs the "--enable-mbstring" when compiled).',0), +25128 => array('The function imageantialias() is not available in your PHP installation. Use the GD version that comes with PHP and not the standalone version.',0), +25129 => array('Anti-alias can not be used with dashed lines. Please disable anti-alias or use solid lines.',0), +/* +**--------------------------------------------------------------------------------------------- +** Pro-version strings +**--------------------------------------------------------------------------------------------- +*/ + +/* +** jpgraph_table +*/ + +27001 => array('GTextTable: Ungültiges Argument für Set(). Das Array-Argument muss 2-- dimensional sein.',0), +27002 => array('GTextTable: Ungültiges Argument für Set()',0), +27003 => array('GTextTable: Falsche Anzahl von Argumenten für GTextTable::SetColor()',0), +27004 => array('GTextTable: Angegebener Zellenbereich, der verschmolzen werden soll, ist ungültig.',0), +27005 => array('GTextTable: Bereits verschmolzene Zellen im Bereich (%d,%d) bis (%d,%d) können nicht ein weiteres Mal verschmolzen werden.',4), +27006 => array('GTextTable: Spalten-Argument = %d liegt außerhalb der festgelegten Tabellengröße.',1), +27007 => array('GTextTable: Zeilen-Argument = %d liegt außerhalb der festgelegten Tabellengröße.',1), +27008 => array('GTextTable: Spalten- und Zeilengröße müssen zu den Dimensionen der Tabelle passen.',0), +27009 => array('GTextTable: Die Anzahl der Tabellenspalten oder -zeilen ist 0. Versichere Dich, dass die Methoden Init() oder Set() aufgerufen werden.',0), +27010 => array('GTextTable: Es wurde keine Ausrichtung beim Aufruf von SetAlign() angegeben.',0), +27011 => array('GTextTable: Es wurde eine unbekannte Ausrichtung beim Aufruf von SetAlign() abgegeben. Horizontal=%s, Vertikal=%s',2), +27012 => array('GTextTable: Interner Fehler. Es wurde ein ungültiges Argument festgeleget %s',1), +27013 => array('GTextTable: Das Argument für FormatNumber() muss ein String sein.',0), +27014 => array('GTextTable: Die Tabelle wurde weder mit einem Aufruf von Set() noch von Init() initialisiert.',0), +27015 => array('GTextTable: Der Zellenbildbedingungstyp muss entweder TIMG_WIDTH oder TIMG_HEIGHT sein.',0), + +/* +** jpgraph_windrose +*/ + +22001 => array('Die Gesamtsumme der prozentualen Anteile aller Windrosenarme darf 100%% nicht überschreiten!\n(Aktuell max: %d)',1), +22002 => array('Das Bild ist zu klein für eine Skala. Bitte vergrößere das Bild.',0), +22004 => array('Die Etikettendefinition für Windrosenrichtungen müssen 16 Werte haben (eine für jede Kompassrichtung).',0), +22005 => array('Der Linientyp für radiale Linien muss einer von ("solid","dotted","dashed","longdashed") sein.',0), +22006 => array('Es wurde ein ungültiger Windrosentyp angegeben.',0), +22007 => array('Es wurden zu wenig Werte für die Bereichslegende angegeben.',0), +22008 => array('Interner Fehler: Versuch, eine freie Windrose zu plotten, obwohl der Typ keine freie Windrose ist.',0), +22009 => array('Du hast die gleiche Richtung zweimal angegeben, einmal mit einem Winkel und einmal mit einer Kompassrichtung (%f Grad).',0), +22010 => array('Die Richtung muss entweder ein numerischer Wert sein oder eine der 16 Kompassrichtungen',0), +22011 => array('Der Windrosenindex muss ein numerischer oder Richtungswert sein. Du hast angegeben Index=%d',1), +22012 => array('Die radiale Achsendefinition für die Windrose enthält eine nicht aktivierte Richtung.',0), +22013 => array('Du hast dasselbe Look&Feel für die gleiche Kompassrichtung zweimal engegeben, einmal mit Text und einmal mit einem Index (Index=%d)',1), +22014 => array('Der Index für eine Kompassrichtung muss zwischen 0 und 15 sein.',0), +22015 => array('Du hast einen unbekannten Windrosenplottyp angegeben.',0), +22016 => array('Der Windrosenarmindex muss ein numerischer oder ein Richtungswert sein.',0), +22017 => array('Die Windrosendaten enthalten eine Richtung, die nicht aktiviert ist. Bitte berichtige, welche Label angezeigt werden sollen.',0), +22018 => array('Du hast für dieselbe Kompassrichtung zweimal Daten angegeben, einmal mit Text und einmal mit einem Index (Index=%d)',1), +22019 => array('Der Index für eine Richtung muss zwischen 0 und 15 sein. Winkel dürfen nicht für einen regelmäßigen Windplot angegeben werden, sondern entweder ein Index oder eine Kompassrichtung.',0), +22020 => array('Der Windrosenplot ist zu groß für die angegebene Bildgröße. Benutze entweder WindrosePlot::SetSize(), um den Plot kleiner zu machen oder vergrößere das Bild im ursprünglichen Aufruf von WindroseGraph().',0), +22021 => array('It is only possible to add Text, IconPlot or WindrosePlot to a Windrose Graph',0), + +/* +** jpgraph_odometer +*/ + +13001 => array('Unbekannter Nadeltypstil (%d).',1), +13002 => array('Ein Wert für das Odometer (%f) ist außerhalb des angegebenen Bereichs [%f,%f]',3), + +/* +** jpgraph_barcode +*/ + +1001 => array('Unbekannte Kodier-Specifikation: %s',1), +1002 => array('datenvalidierung schlug fehl. [%s] kann nicht mittels der Kodierung "%s" kodiert werden',2), +1003 => array('Interner Kodierfehler. Kodieren von %s ist nicht möglich in Code 128',1), +1004 => array('Interner barcode Fehler. Unbekannter UPC-E Kodiertyp: %s',1), +1005 => array('Interner Fehler. Das Textzeichen-Tupel (%s, %s) kann nicht im Code-128 Zeichensatz C kodiert werden.',2), +1006 => array('Interner Kodierfehler für CODE 128. Es wurde versucht, CTRL in CHARSET != A zu kodieren.',0), +1007 => array('Interner Kodierfehler für CODE 128. Es wurde versucht, DEL in CHARSET != B zu kodieren.',0), +1008 => array('Interner Kodierfehler für CODE 128. Es wurde versucht, kleine Buchstaben in CHARSET != B zu kodieren.',0), +1009 => array('Kodieren mittels CODE 93 wird noch nicht unterstützt.',0), +1010 => array('Kodieren mittels POSTNET wird noch nicht unterstützt.',0), +1011 => array('Nicht untrstütztes Barcode-Backend für den Typ %s',1), + +/* +** PDF417 +*/ + +26001 => array('PDF417: Die Anzahl der Spalten muss zwischen 1 und 30 sein.',0), +26002 => array('PDF417: Der Fehler-Level muss zwischen 0 und 8 sein.',0), +26003 => array('PDF417: Ungültiges Format für Eingabedaten, um sie mit PDF417 zu kodieren.',0), +26004 => array('PDF417: die eigebenen Daten können nicht mit Fehler-Level %d und %d spalten kodiert werden, weil daraus zu viele Symbole oder mehr als 90 Zeilen resultieren.',2), +26005 => array('PDF417: Die Datei "%s" kann nicht zum Schreiben geöffnet werden.',1), +26006 => array('PDF417: Interner Fehler. Die Eingabedatendatei für PDF417-Cluster %d ist fehlerhaft.',1), +26007 => array('PDF417: Interner Fehler. GetPattern: Ungültiger Code-Wert %d (Zeile %d)',2), +26008 => array('PDF417: Interner Fehler. Modus wurde nicht in der Modusliste!! Modus %d',1), +26009 => array('PDF417: Kodierfehler: Ungültiges Zeichen. Zeichen kann nicht mit ASCII-Code %d kodiert werden.',1), +26010 => array('PDF417: Interner Fehler: Keine Eingabedaten beim Dekodieren.',0), +26011 => array('PDF417: Kodierfehler. Numerisches Kodieren bei nicht-numerischen Daten nicht möglich.',0), +26012 => array('PDF417: Interner Fehler. Es wurden für den Binary-Kompressor keine Daten zum Dekodieren eingegeben.',0), +26013 => array('PDF417: Interner Fehler. Checksum Fehler. Koeffiziententabellen sind fehlerhaft.',0), +26014 => array('PDF417: Interner Fehler. Es wurden keine Daten zum Berechnen von Kodewörtern eingegeben.',0), +26015 => array('PDF417: Interner Fehler. Ein Eintrag 0 in die Statusübertragungstabellen ist nicht NULL. Eintrag 1 = (%s)',1), +26016 => array('PDF417: Interner Fehler: Nichtregistrierter Statusübertragungsmodus beim Dekodieren.',0), + + +); + +?> diff --git a/src/classes/jpgraph/lang/en.inc.php b/src/classes/jpgraph/lang/en.inc.php new file mode 100644 index 0000000..e61080e --- /dev/null +++ b/src/classes/jpgraph/lang/en.inc.php @@ -0,0 +1,500 @@ +,) +$_jpg_messages = array( + +/* +** Headers already sent error. This is formatted as HTML different since this will be sent back directly as text +*/ +10 => array('
    JpGraph Error: +HTTP headers have already been sent.
    Caused by output from file %s at line %d.
    Explanation:
    HTTP headers have already been sent back to the browser indicating the data as text before the library got a chance to send it\'s image HTTP header to this browser. This makes it impossible for the library to send back image data to the browser (since that would be interpretated as text by the browser and show up as junk text).

    Most likely you have some text in your script before the call to Graph::Stroke(). If this texts gets sent back to the browser the browser will assume that all data is plain text. Look for any text, even spaces and newlines, that might have been sent back to the browser.

    For example it is a common mistake to leave a blank line before the opening "<?php".

    ',2), + +/* +** Setup errors +*/ +11 => array('No path specified for CACHE_DIR. Please specify CACHE_DIR manually in jpg-config.inc',0), +12 => array('No path specified for TTF_DIR and path can not be determined automatically. Please specify TTF_DIR manually (in jpg-config.inc).',0), +13 => array('The installed PHP version (%s) is not compatible with this release of the library. The library requires at least PHP version %s',2), + + +/* +** jpgraph_bar +*/ + +2001 => array('Number of colors is not the same as the number of patterns in BarPlot::SetPattern()',0), +2002 => array('Unknown pattern specified in call to BarPlot::SetPattern()',0), +2003 => array('Number of X and Y points are not equal. Number of X-points: %d Number of Y-points: %d',2), +2004 => array('All values for a barplot must be numeric. You have specified value nr [%d] == %s',2), +2005 => array('You have specified an empty array for shadow colors in the bar plot.',0), +2006 => array('Unknown position for values on bars : %s',1), +2007 => array('Cannot create GroupBarPlot from empty plot array.',0), +2008 => array('Group bar plot element nbr %d is undefined or empty.',0), +2009 => array('One of the objects submitted to GroupBar is not a BarPlot. Make sure that you create the GroupBar plot from an array of BarPlot or AccBarPlot objects. (Class = %s)',1), +2010 => array('Cannot create AccBarPlot from empty plot array.',0), +2011 => array('Acc bar plot element nbr %d is undefined or empty.',1), +2012 => array('One of the objects submitted to AccBar is not a BarPlot. Make sure that you create the AccBar plot from an array of BarPlot objects. (Class=%s)',1), +2013 => array('You have specified an empty array for shadow colors in the bar plot.',0), +2014 => array('Number of datapoints for each data set in accbarplot must be the same',0), + + +/* +** jpgraph_date +*/ + +3001 => array('It is only possible to use either SetDateAlign() or SetTimeAlign() but not both',0), + +/* +** jpgraph_error +*/ + +4002 => array('Error in input data to LineErrorPlot. Number of data points must be a multiple of 3',0), + +/* +** jpgraph_flags +*/ + +5001 => array('Unknown flag size (%d).',1), +5002 => array('Flag index %s does not exist.',1), +5003 => array('Invalid ordinal number (%d) specified for flag index.',1), +5004 => array('The (partial) country name %s does not have a corresponding flag image. The flag may still exist but under another name, e.g. instead of "usa" try "united states".',1), + + +/* +** jpgraph_gantt +*/ + +6001 => array('Internal error. Height for ActivityTitles is < 0',0), +6002 => array('You can\'t specify negative sizes for Gantt graph dimensions. Use 0 to indicate that you want the library to automatically determine a dimension.',0), +6003 => array('Invalid format for Constrain parameter at index=%d in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Constrain-To,Constrain-Type)',1), +6004 => array('Invalid format for Progress parameter at index=%d in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Progress)',1), +6005 => array('SetScale() is not meaningful with Gantt charts.',0), +6006 => array('Cannot autoscale Gantt chart. No dated activities exist. [GetBarMinMax() start >= n]',0), +6007 => array('Sanity check for automatic Gantt chart size failed. Either the width (=%d) or height (=%d) is larger than MAX_GANTTIMG_SIZE. This could potentially be caused by a wrong date in one of the activities.',2), +6008 => array('You have specified a constrain from row=%d to row=%d which does not have any activity',2), +6009 => array('Unknown constrain type specified from row=%d to row=%d',2), +6010 => array('Illegal icon index for Gantt builtin icon [%d]',1), +6011 => array('Argument to IconImage must be string or integer',0), +6012 => array('Unknown type in Gantt object title specification',0), +6015 => array('Illegal vertical position %d',1), +6016 => array('Date string (%s) specified for Gantt activity can not be interpretated. Please make sure it is a valid time string, e.g. 2005-04-23 13:30',1), +6017 => array('Unknown date format in GanttScale (%s).',1), +6018 => array('Interval for minutes must divide the hour evenly, e.g. 1,5,10,12,15,20,30 etc You have specified an interval of %d minutes.',1), +6019 => array('The available width (%d) for minutes are to small for this scale to be displayed. Please use auto-sizing or increase the width of the graph.',1), +6020 => array('Interval for hours must divide the day evenly, e.g. 0:30, 1:00, 1:30, 4:00 etc. You have specified an interval of %d',1), +6021 => array('Unknown formatting style for week.',0), +6022 => array('Gantt scale has not been specified.',0), +6023 => array('If you display both hour and minutes the hour interval must be 1 (Otherwise it doesn\'t make sense to display minutes).',0), +6024 => array('CSIM Target must be specified as a string. Start of target is: %d',1), +6025 => array('CSIM Alt text must be specified as a string. Start of alt text is: %d',1), +6027 => array('Progress value must in range [0, 1]',0), +6028 => array('Specified height (%d) for gantt bar is out of range.',1), +6029 => array('Offset for vertical line must be in range [0,1]',0), +6030 => array('Unknown arrow direction for link.',0), +6031 => array('Unknown arrow type for link.',0), +6032 => array('Internal error: Unknown path type (=%d) specified for link.',1), + +/* +** jpgraph_gradient +*/ + +7001 => array('Unknown gradient style (=%d).',1), + +/* +** jpgraph_iconplot +*/ + +8001 => array('Mix value for icon must be between 0 and 100.',0), +8002 => array('Anchor position for icons must be one of "top", "bottom", "left", "right" or "center"',0), +8003 => array('It is not possible to specify both an image file and a country flag for the same icon.',0), +8004 => array('In order to use Country flags as icons you must include the "jpgraph_flags.php" file.',0), + +/* +** jpgraph_imgtrans +*/ + +9001 => array('Value for image transformation out of bounds. Vanishing point on horizon must be specified as a value between 0 and 1.',0), + +/* +** jpgraph_lineplot +*/ + +10001 => array('LinePlot::SetFilled() is deprecated. Use SetFillColor()',0), +10002 => array('Plot too complicated for fast line Stroke. Use standard Stroke()',0), +10003 => array('Each plot in an accumulated lineplot must have the same number of data points.',0), + +/* +** jpgraph_log +*/ + +11001 => array('Your data contains non-numeric values.',0), +11002 => array('Negative data values can not be used in a log scale.',0), +11003 => array('Your data contains non-numeric values.',0), +11004 => array('Scale error for logarithmic scale. You have a problem with your data values. The max value must be greater than 0. It is mathematically impossible to have 0 in a logarithmic scale.',0), +11005 => array('Specifying tick interval for a logarithmic scale is undefined. Remove any calls to SetTextLabelStart() or SetTextTickInterval() on the logarithmic scale.',0), + +/* +** jpgraph_mgraph +*/ + +12001 => array("You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x it is necessary to enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts.",0), +12002 => array('Incorrect file name for MGraph::SetBackgroundImage() : %s Must have a valid image extension (jpg,gif,png) when using auto detection of image type',1), +12003 => array('Unknown file extension (%s) in MGraph::SetBackgroundImage() for filename: %s',2), +12004 => array('The image format of your background image (%s) is not supported in your system configuration. ',1), +12005 => array('Can\'t read background image: %s',1), +12006 => array('Illegal sizes specified for width or height when creating an image, (width=%d, height=%d)',2), +12007 => array('Argument to MGraph::Add() is not a valid GD image handle.',0), +12008 => array('Your PHP (and GD-lib) installation does not appear to support any known graphic formats.',0), +12009 => array('Your PHP installation does not support the chosen graphic format: %s',1), +12010 => array('Can\'t create or stream image to file %s Check that PHP has enough permission to write a file to the current directory.',1), +12011 => array('Can\'t create truecolor image. Check that you really have GD2 library installed.',0), +12012 => array('Can\'t create image. Check that you really have GD2 library installed.',0), + +/* +** jpgraph_pie3d +*/ + +14001 => array('Pie3D::ShowBorder() . Deprecated function. Use Pie3D::SetEdge() to control the edges around slices.',0), +14002 => array('PiePlot3D::SetAngle() 3D Pie projection angle must be between 5 and 85 degrees.',0), +14003 => array('Internal assertion failed. Pie3D::Pie3DSlice',0), +14004 => array('Slice start angle must be between 0 and 360 degrees.',0), +14005 => array('Pie3D Internal error: Trying to wrap twice when looking for start index',0,), +14006 => array('Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.',0), +14007 => array('Width for 3D Pie is 0. Specify a size > 0',0), + +/* +** jpgraph_pie +*/ + +15001 => array('PiePLot::SetTheme() Unknown theme: %s',1), +15002 => array('Argument to PiePlot::ExplodeSlice() must be an integer',0), +15003 => array('Argument to PiePlot::Explode() must be an array with integer distances.',0), +15004 => array('Slice start angle must be between 0 and 360 degrees.',0), +15005 => array('PiePlot::SetFont() is deprecated. Use PiePlot->value->SetFont() instead.',0), +15006 => array('PiePlot::SetSize() Radius for pie must either be specified as a fraction [0, 0.5] of the size of the image or as an absolute size in pixels in the range [10, 1000]',0), +15007 => array('PiePlot::SetFontColor() is deprecated. Use PiePlot->value->SetColor() instead.',0), +15008 => array('PiePlot::SetLabelType() Type for pie plots must be 0 or 1 (not %d).',1), +15009 => array('Illegal pie plot. Sum of all data is zero for Pie Plot',0), +15010 => array('Sum of all data is 0 for Pie.',0), +15011 => array('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.',0), + +/* +** jpgraph_plotband +*/ + +16001 => array('Density for pattern must be between 1 and 100. (You tried %f)',1), +16002 => array('No positions specified for pattern.',0), +16003 => array('Unknown pattern specification (%d)',0), +16004 => array('Min value for plotband is larger than specified max value. Please correct.',0), + + +/* +** jpgraph_polar +*/ + +17001 => array('Polar plots must have an even number of data point. Each data point is a tuple (angle,radius).',0), +17002 => array('Unknown alignment specified for X-axis title. (%s)',1), +//17003 => array('Set90AndMargin() is not supported for polar graphs.',0), +17004 => array('Unknown scale type for polar graph. Must be "lin" or "log"',0), + +/* +** jpgraph_radar +*/ + +18001 => array('Client side image maps not supported for RadarPlots.',0), +18002 => array('RadarGraph::SupressTickMarks() is deprecated. Use HideTickMarks() instead.',0), +18003 => array('Illegal scale for radarplot (%s). Must be \'lin\' or \'log\'',1), +18004 => array('Radar Plot size must be between 0.1 and 1. (Your value=%f)',1), +18005 => array('RadarPlot Unsupported Tick density: %d',1), +18006 => array('Minimum data %f (Radar plots should only be used when all data points > 0)',1), +18007 => array('Number of titles does not match number of points in plot.',0), +18008 => array('Each radar plot must have the same number of data points.',0), + +/* +** jpgraph_regstat +*/ + +19001 => array('Spline: Number of X and Y coordinates must be the same',0), +19002 => array('Invalid input data for spline. Two or more consecutive input X-values are equal. Each input X-value must differ since from a mathematical point of view it must be a one-to-one mapping, i.e. each X-value must correspond to exactly one Y-value.',0), +19003 => array('Bezier: Number of X and Y coordinates must be the same',0), + +/* +** jpgraph_scatter +*/ + +20001 => array('Fieldplots must have equal number of X and Y points.',0), +20002 => array('Fieldplots must have an angle specified for each X and Y points.',0), +20003 => array('Scatterplot must have equal number of X and Y points.',0), + +/* +** jpgraph_stock +*/ + +21001 => array('Data values for Stock charts must contain an even multiple of %d data points.',1), + +/* +** jpgraph_plotmark +*/ + +23001 => array('This marker "%s" does not exist in color with index: %d',2), +23002 => array('Mark color index too large for marker "%s"',1), +23003 => array('A filename must be specified if you set the mark type to MARK_IMG.',0), + +/* +** jpgraph_utils +*/ + +24001 => array('FuncGenerator : No function specified. ',0), +24002 => array('FuncGenerator : Syntax error in function specification ',0), +24003 => array('DateScaleUtils: Unknown tick type specified in call to GetTicks()',0), +/* +** jpgraph +*/ + +25001 => array('This PHP installation is not configured with the GD library. Please recompile PHP with GD support to run JpGraph. (Neither function imagetypes() nor imagecreatefromstring() does exist)',0), +25002 => array('Your PHP installation does not seem to have the required GD library. Please see the PHP documentation on how to install and enable the GD library.',0), +25003 => array('General PHP error : At %s:%d : %s',3), +25004 => array('General PHP error : %s ',1), +25005 => array('Can\'t access PHP_SELF, PHP global variable. You can\'t run PHP from command line if you want to use the \'auto\' naming of cache or image files.',0), +25006 => array('Usage of FF_CHINESE (FF_BIG5) font family requires that your PHP setup has the iconv() function. By default this is not compiled into PHP (needs the "--width-iconv" when configured).',0), +25007 => array('You are trying to use the locale (%s) which your PHP installation does not support. Hint: Use \'\' to indicate the default locale for this geographic region.',1), +25008 => array('Image width/height argument in Graph::Graph() must be numeric',0), +25009 => array('You must specify what scale to use with a call to Graph::SetScale()',0), + +25010 => array('Graph::Add() You tried to add a null plot to the graph.',0), +25011 => array('Graph::AddY2() You tried to add a null plot to the graph.',0), +25012 => array('Graph::AddYN() You tried to add a null plot to the graph.',0), +25013 => array('You can only add standard plots to multiple Y-axis',0), +25014 => array('Graph::AddText() You tried to add a null text to the graph.',0), +25015 => array('Graph::AddLine() You tried to add a null line to the graph.',0), +25016 => array('Graph::AddBand() You tried to add a null band to the graph.',0), +25017 => array('You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x it is necessary to enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts.',0), +25018 => array('Incorrect file name for Graph::SetBackgroundImage() : "%s" Must have a valid image extension (jpg,gif,png) when using auto detection of image type',1), +25019 => array('Unknown file extension (%s) in Graph::SetBackgroundImage() for filename: "%s"',2), + +25020 => array('Graph::SetScale(): Specified Max value must be larger than the specified Min value.',0), +25021 => array('Unknown scale specification for Y-scale. (%s)',1), +25022 => array('Unknown scale specification for X-scale. (%s)',1), +25023 => array('Unsupported Y2 axis type: "%s" Must be one of (lin,log,int)',1), +25024 => array('Unsupported Y axis type: "%s" Must be one of (lin,log,int)',1), +25025 => array('Unsupported Tick density: %d',1), +25026 => array('Can\'t draw unspecified Y-scale. You have either: 1. Specified an Y axis for auto scaling but have not supplied any plots. 2. Specified a scale manually but have forgot to specify the tick steps',0), +25027 => array('Can\'t open cached CSIM "%s" for reading.',1), +25028 => array('Apache/PHP does not have permission to write to the CSIM cache directory (%s). Check permissions.',1), +25029 => array('Can\'t write CSIM "%s" for writing. Check free space and permissions.',1), + +25030 => array('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().',0), +25031 => array('You must specify what scale to use with a call to Graph::SetScale().',0), +25032 => array('No plots for Y-axis nbr:%d',1), +25033 => array('',0), +25034 => array('Can\'t draw unspecified X-scale. No plots specified.',0), +25035 => array('You have enabled clipping. Clipping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (=%d degrees) or disable clipping.',1), +25036 => array('Unknown AxisStyle() : %s',1), +25037 => array('The image format of your background image (%s) is not supported in your system configuration. ',1), +25038 => array('Background image seems to be of different type (has different file extension) than specified imagetype. Specified: %s File: %s',2), +25039 => array('Can\'t read background image: "%s"',1), + +25040 => array('It is not possible to specify both a background image and a background country flag.',0), +25041 => array('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.',0), +25042 => array('Unknown background image layout',0), +25043 => array('Unknown title background style.',0), +25044 => array('Cannot use auto scaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).',0), +25045 => array('Font families FF_HANDWRT and FF_BOOK are no longer available due to copyright problem with these fonts. Fonts can no longer be distributed with JpGraph. Please download fonts from http://corefonts.sourceforge.net/',0), +25046 => array('Specified TTF font family (id=%d) is unknown or does not exist. Please note that TTF fonts are not distributed with JpGraph for copyright reasons. You can find the MS TTF WEB-fonts (arial, courier etc) for download at http://corefonts.sourceforge.net/',1), +25047 => array('Style %s is not available for font family %s',2), +25048 => array('Unknown font style specification [%s].',1), +25049 => array('Font file "%s" is not readable or does not exist.',1), + +25050 => array('First argument to Text::Text() must be a string.',0), +25051 => array('Invalid direction specified for text.',0), +25052 => array('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text',0), +25053 => array('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text',0), +25054 => array('Internal error: Unknown grid axis %s',1), +25055 => array('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead',0), +25056 => array('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.',0), +25057 => array('SetTextTicks() is deprecated. Use SetTextTickInterval() instead.',0), +25058 => array('Text label interval must be specified >= 1.',0), +25059 => array('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.',0), + +25060 => array('Unknown alignment specified for X-axis title. (%s)',1), +25061 => array('Unknown alignment specified for Y-axis title. (%s)',1), +25062 => array('Labels at an angle are not supported on Y-axis',0), +25063 => array('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead',0), +25064 => array('Minor or major step size is 0. Check that you haven\'t got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem',0), +25065 => array('Tick positions must be specified as an array()',0), +25066 => array('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.',0), +25067 => array('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tick marks.',0), +25068 => array('A plot has an illegal scale. This could for example be that you are trying to use text auto scaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only \'-\' or \'x\')',0), +25069 => array('Grace must be larger then 0',0), +25070 => array('Either X or Y data arrays contains non-numeric values. Check that the data is really specified as numeric data and not as strings. It is an error to specify data for example as \'-2345.2\' (using quotes).',0), +25071 => array('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.',0), +25072 => array('You have specified a max value with SetAutoMax() which is smaller than the minimum value used for the scale. This is not possible.',0), +25073 => array('Internal error. Integer scale algorithm comparison out of bound (r=%f)',1), +25074 => array('Internal error. The scale range is negative (%f) [for %s scale] This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the auto scaling to fail.',2), +25075 => array('Can\'t automatically determine ticks since min==max.',0), +25077 => array('Adjustment factor for color must be > 0',0), +25078 => array('Unknown color: %s',1), +25079 => array('Unknown color specification: %s, size=%d',2), + +25080 => array('Alpha parameter for color must be between 0.0 and 1.0',0), +25081 => array('Selected graphic format is either not supported or unknown [%s]',1), +25082 => array('Illegal sizes specified for width or height when creating an image, (width=%d, height=%d)',2), +25083 => array('Illegal image size when copying image. Size for copied to image is 1 pixel or less.',0), +25084 => array('Failed to create temporary GD canvas. Possible Out of memory problem.',0), +25085 => array('An image can not be created from the supplied string. It is either in a format not supported or the string is representing an corrupt image.',0), +25086 => array('You only seem to have GD 1.x installed. To enable Alphablending requires GD 2.x or higher. Please install GD or make sure the constant USE_GD2 is specified correctly to reflect your installation. By default it tries to auto detect what version of GD you have installed. On some very rare occasions it may falsely detect GD2 where only GD1 is installed. You must then set USE_GD2 to false.',0), +25087 => array('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.',0), +25088 => array('You have a misconfigured GD font support. The call to imagefontwidth() fails.',0), +25089 => array('You have a misconfigured GD font support. The call to imagefontheight() fails.',0), + +25090 => array('Unknown direction specified in call to StrokeBoxedText() [%s]',1), +25091 => array('Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.',0), +25092 => array('There is either a configuration problem with TrueType or a problem reading font file "%s" Make sure file exists and is in a readable place for the HTTP process. (If \'basedir\' restriction is enabled in PHP then the font file must be located in the document root.). It might also be a wrongly installed FreeType library. Try upgrading to at least FreeType 2.1.13 and recompile GD with the correct setup so it can find the new FT library.',1), +25093 => array('Can not read font file "%s" in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.',1), +25094 => array('Direction for text most be given as an angle between 0 and 90.',0), +25095 => array('Unknown font font family specification. ',0), +25096 => array('Can\'t allocate any more colors in palette image. Image has already allocated maximum of %d colors and the palette is now full. Change to a truecolor image instead',0), +25097 => array('Color specified as empty string in PushColor().',0), +25098 => array('Negative Color stack index. Unmatched call to PopColor()',0), +25099 => array('Parameters for brightness and Contrast out of range [-1,1]',0), + +25100 => array('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and truecolor is enabled.',0), +25101 => array('Illegal numeric argument to SetLineStyle(): (%d)',1), +25102 => array('Illegal string argument to SetLineStyle(): %s',1), +25103 => array('Illegal argument to SetLineStyle %s',1), +25104 => array('Unknown line style: %s',1), +25105 => array('NULL data specified for a filled polygon. Check that your data is not NULL.',0), +25106 => array('Image::FillToBorder : Can not allocate more colors',0), +25107 => array('Can\'t write to file "%s". Check that the process running PHP has enough permission.',1), +25108 => array('Can\'t stream image. This is most likely due to a faulty PHP/GD setup. Try to recompile PHP and use the built-in GD library that comes with PHP.',0), +25109 => array('Your PHP (and GD-lib) installation does not appear to support any known graphic formats. You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images you must get the JPEG library. Please see the PHP docs for details.',0), + +25110 => array('Your PHP installation does not support the chosen graphic format: %s',1), +25111 => array('Can\'t delete cached image %s. Permission problem?',1), +25112 => array('Cached imagefile (%s) has file date in the future.',1), +25113 => array('Can\'t delete cached image "%s". Permission problem?',1), +25114 => array('PHP has not enough permissions to write to the cache file "%s". Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.',1), +25115 => array('Can\'t set permission for cached image "%s". Permission problem?',1), +25116 => array('Cant open file from cache "%s"',1), +25117 => array('Can\'t open cached image "%s" for reading.',1), +25118 => array('Can\'t create directory "%s". Make sure PHP has write permission to this directory.',1), +25119 => array('Can\'t set permissions for "%s". Permission problems?',1), + +25120 => array('Position for legend must be given as percentage in range 0-1',0), +25121 => array('Empty input data array specified for plot. Must have at least one data point.',0), +25122 => array('Stroke() must be implemented by concrete subclass to class Plot',0), +25123 => array('You can\'t use a text X-scale with specified X-coords. Use a "int" or "lin" scale instead.',0), +25124 => array('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)',0), +25125 => array('Illegal direction for static line',0), +25126 => array('Can\'t create truecolor image. Check that the GD2 library is properly setup with PHP.',0), +25127 => array('The library has been configured for automatic encoding conversion of Japanese fonts. This requires that PHP has the mb_convert_encoding() function. Your PHP installation lacks this function (PHP needs the "--enable-mbstring" when compiled).',0), +25128 => array('The function imageantialias() is not available in your PHP installation. Use the GD version that comes with PHP and not the standalone version.',0), +25129 => array('Anti-alias can not be used with dashed lines. Please disable anti-alias or use solid lines.',0), + + +/* +**--------------------------------------------------------------------------------------------- +** Pro-version strings +**--------------------------------------------------------------------------------------------- +*/ + +/* +** jpgraph_table +*/ + +27001 => array('GTextTable: Invalid argument to Set(). Array argument must be 2 dimensional',0), +27002 => array('GTextTable: Invalid argument to Set()',0), +27003 => array('GTextTable: Wrong number of arguments to GTextTable::SetColor()',0), +27004 => array('GTextTable: Specified cell range to be merged is not valid.',0), +27005 => array('GTextTable: Cannot merge already merged cells in the range: (%d,%d) to (%d,%d)',4), +27006 => array('GTextTable: Column argument = %d is outside specified table size.',1), +27007 => array('GTextTable: Row argument = %d is outside specified table size.',1), +27008 => array('GTextTable: Column and row size arrays must match the dimensions of the table',0), +27009 => array('GTextTable: Number of table columns or rows are 0. Make sure Init() or Set() is called.',0), +27010 => array('GTextTable: No alignment specified in call to SetAlign()',0), +27011 => array('GTextTable: Unknown alignment specified in SetAlign(). Horizontal=%s, Vertical=%s',2), +27012 => array('GTextTable: Internal error. Invalid alignment specified =%s',1), +27013 => array('GTextTable: Argument to FormatNumber() must be a string.',0), +27014 => array('GTextTable: Table is not initilaized with either a call to Set() or Init()',0), +27015 => array('GTextTable: Cell image constrain type must be TIMG_WIDTH or TIMG_HEIGHT',0), + +/* +** jpgraph_windrose +*/ + +22001 => array('Total percentage for all windrose legs in a windrose plot can not exceed 100%% !\n(Current max is: %d)',1), +22002 => array('Graph is too small to have a scale. Please make the graph larger.',0), +22004 => array('Label specification for windrose directions must have 16 values (one for each compass direction).',0), +22005 => array('Line style for radial lines must be on of ("solid","dotted","dashed","longdashed") ',0), +22006 => array('Illegal windrose type specified.',0), +22007 => array('To few values for the range legend.',0), +22008 => array('Internal error: Trying to plot free Windrose even though type is not a free windrose',0), +22009 => array('You have specified the same direction twice, once with an angle and once with a compass direction (%f degrees)',0), +22010 => array('Direction must either be a numeric value or one of the 16 compass directions',0), +22011 => array('Windrose index must be numeric or direction label. You have specified index=%d',1), +22012 => array('Windrose radial axis specification contains a direction which is not enabled.',0), +22013 => array('You have specified the look&feel for the same compass direction twice, once with text and once with index (Index=%d)',1), +22014 => array('Index for compass direction must be between 0 and 15.',0), +22015 => array('You have specified an undefined Windrose plot type.',0), +22016 => array('Windrose leg index must be numeric or direction label.',0), +22017 => array('Windrose data contains a direction which is not enabled. Please adjust what labels are displayed.',0), +22018 => array('You have specified data for the same compass direction twice, once with text and once with index (Index=%d)',1), +22019 => array('Index for direction must be between 0 and 15. You can\'t specify angles for a Regular Windplot, only index and compass directions.',0), +22020 => array('Windrose plot is too large to fit the specified Graph size. Please use WindrosePlot::SetSize() to make the plot smaller or increase the size of the Graph in the initial WindroseGraph() call.',0), +22021 => array('It is only possible to add Text, IconPlot or WindrosePlot to a Windrose Graph',0), +/* +** jpgraph_odometer +*/ + +13001 => array('Unknown needle style (%d).',1), +13002 => array('Value for odometer (%f) is outside specified scale [%f,%f]',3), + +/* +** jpgraph_barcode +*/ + +1001 => array('Unknown encoder specification: %s',1), +1002 => array('Data validation failed. Can\'t encode [%s] using encoding "%s"',2), +1003 => array('Internal encoding error. Trying to encode %s is not possible in Code 128',1), +1004 => array('Internal barcode error. Unknown UPC-E encoding type: %s',1), +1005 => array('Internal error. Can\'t encode character tuple (%s, %s) in Code-128 charset C',2), +1006 => array('Internal encoding error for CODE 128. Trying to encode control character in CHARSET != A',0), +1007 => array('Internal encoding error for CODE 128. Trying to encode DEL in CHARSET != B',0), +1008 => array('Internal encoding error for CODE 128. Trying to encode small letters in CHARSET != B',0), +1009 => array('Encoding using CODE 93 is not yet supported.',0), +1010 => array('Encoding using POSTNET is not yet supported.',0), +1011 => array('Non supported barcode backend for type %s',1), + +/* +** PDF417 +*/ + +26001 => array('PDF417: Number of Columns must be >= 1 and <= 30',0), +26002 => array('PDF417: Error level must be between 0 and 8',0), +26003 => array('PDF417: Invalid format for input data to encode with PDF417',0), +26004 => array('PDF417: Can\'t encode given data with error level %d and %d columns since it results in too many symbols or more than 90 rows.',2), +26005 => array('PDF417: Can\'t open file "%s" for writing',1), +26006 => array('PDF417: Internal error. Data files for PDF417 cluster %d is corrupted.',1), +26007 => array('PDF417: Internal error. GetPattern: Illegal Code Value = %d (row=%d)',2), +26008 => array('PDF417: Internal error. Mode not found in mode list!! mode=%d',1), +26009 => array('PDF417: Encode error: Illegal character. Can\'t encode character with ASCII code=%d',1), +26010 => array('PDF417: Internal error: No input data in decode.',0), +26011 => array('PDF417: Encoding error. Can\'t use numeric encoding on non-numeric data.',0), +26012 => array('PDF417: Internal error. No input data to decode for Binary compressor.',0), +26013 => array('PDF417: Internal error. Checksum error. Coefficient tables corrupted.',0), +26014 => array('PDF417: Internal error. No data to calculate codewords on.',0), +26015 => array('PDF417: Internal error. State transition table entry 0 is NULL. Entry 1 = (%s)',1), +26016 => array('PDF417: Internal error: Unrecognized state transition mode in decode.',0), + + +); + +?> diff --git a/src/classes/jpgraph/lang/prod.inc.php b/src/classes/jpgraph/lang/prod.inc.php new file mode 100644 index 0000000..f04e30d --- /dev/null +++ b/src/classes/jpgraph/lang/prod.inc.php @@ -0,0 +1,357 @@ +,) +$_jpg_messages = array( + +/* +** Headers already sent error. This is formatted as HTML different since this will be sent back directly as text +*/ +10 => array('
    JpGraph Error: +HTTP headers have already been sent.
    Caused by output from file %s at line %d.
    Explanation:
    HTTP headers have already been sent back to the browser indicating the data as text before the library got a chance to send it\'s image HTTP header to this browser. This makes it impossible for the library to send back image data to the browser (since that would be interpretated as text by the browser and show up as junk text).

    Most likely you have some text in your script before the call to Graph::Stroke(). If this texts gets sent back to the browser the browser will assume that all data is plain text. Look for any text, even spaces and newlines, that might have been sent back to the browser.

    For example it is a common mistake to leave a blank line before the opening "<?php".

    ',2), + + +11 => array(DEFAULT_ERROR_MESSAGE.'11',0), +12 => array(DEFAULT_ERROR_MESSAGE.'12',0), +13 => array(DEFAULT_ERROR_MESSAGE.'13',0), +2001 => array(DEFAULT_ERROR_MESSAGE.'2001',0), +2002 => array(DEFAULT_ERROR_MESSAGE.'2002',0), +2003 => array(DEFAULT_ERROR_MESSAGE.'2003',0), +2004 => array(DEFAULT_ERROR_MESSAGE.'2004',0), +2005 => array(DEFAULT_ERROR_MESSAGE.'2005',0), +2006 => array(DEFAULT_ERROR_MESSAGE.'2006',0), +2007 => array(DEFAULT_ERROR_MESSAGE.'2007',0), +2008 => array(DEFAULT_ERROR_MESSAGE.'2008',0), +2009 => array(DEFAULT_ERROR_MESSAGE.'2009',0), +2010 => array(DEFAULT_ERROR_MESSAGE.'2010',0), +2011 => array(DEFAULT_ERROR_MESSAGE.'2011',0), +2012 => array(DEFAULT_ERROR_MESSAGE.'2012',0), +2013 => array(DEFAULT_ERROR_MESSAGE.'2013',0), +2014 => array(DEFAULT_ERROR_MESSAGE.'2014',0), +3001 => array(DEFAULT_ERROR_MESSAGE.'3001',0), +4002 => array(DEFAULT_ERROR_MESSAGE.'4002',0), +5001 => array(DEFAULT_ERROR_MESSAGE.'5001',0), +5002 => array(DEFAULT_ERROR_MESSAGE.'5002',0), +5003 => array(DEFAULT_ERROR_MESSAGE.'5003',0), +5004 => array(DEFAULT_ERROR_MESSAGE.'5004',0), +6001 => array(DEFAULT_ERROR_MESSAGE.'6001',0), +6002 => array(DEFAULT_ERROR_MESSAGE.'6002',0), +6003 => array(DEFAULT_ERROR_MESSAGE.'6003',0), +6004 => array(DEFAULT_ERROR_MESSAGE.'6004',0), +6005 => array(DEFAULT_ERROR_MESSAGE.'6005',0), +6006 => array(DEFAULT_ERROR_MESSAGE.'6006',0), +6007 => array(DEFAULT_ERROR_MESSAGE.'6007',0), +6008 => array(DEFAULT_ERROR_MESSAGE.'6008',0), +6009 => array(DEFAULT_ERROR_MESSAGE.'6009',0), +6010 => array(DEFAULT_ERROR_MESSAGE.'6010',0), +6011 => array(DEFAULT_ERROR_MESSAGE.'6011',0), +6012 => array(DEFAULT_ERROR_MESSAGE.'6012',0), +6015 => array(DEFAULT_ERROR_MESSAGE.'6015',0), +6016 => array(DEFAULT_ERROR_MESSAGE.'6016',0), +6017 => array(DEFAULT_ERROR_MESSAGE.'6017',0), +6018 => array(DEFAULT_ERROR_MESSAGE.'6018',0), +6019 => array(DEFAULT_ERROR_MESSAGE.'6019',0), +6020 => array(DEFAULT_ERROR_MESSAGE.'6020',0), +6021 => array(DEFAULT_ERROR_MESSAGE.'6021',0), +6022 => array(DEFAULT_ERROR_MESSAGE.'6022',0), +6023 => array(DEFAULT_ERROR_MESSAGE.'6023',0), +6024 => array(DEFAULT_ERROR_MESSAGE.'6024',0), +6025 => array(DEFAULT_ERROR_MESSAGE.'6025',0), +6027 => array(DEFAULT_ERROR_MESSAGE.'6027',0), +6028 => array(DEFAULT_ERROR_MESSAGE.'6028',0), +6029 => array(DEFAULT_ERROR_MESSAGE.'6029',0), +6030 => array(DEFAULT_ERROR_MESSAGE.'6030',0), +6031 => array(DEFAULT_ERROR_MESSAGE.'6031',0), +6032 => array(DEFAULT_ERROR_MESSAGE.'6032',0), +7001 => array(DEFAULT_ERROR_MESSAGE.'7001',0), +8001 => array(DEFAULT_ERROR_MESSAGE.'8001',0), +8002 => array(DEFAULT_ERROR_MESSAGE.'8002',0), +8003 => array(DEFAULT_ERROR_MESSAGE.'8003',0), +8004 => array(DEFAULT_ERROR_MESSAGE.'8004',0), +9001 => array(DEFAULT_ERROR_MESSAGE.'9001',0), +10001 => array(DEFAULT_ERROR_MESSAGE.'10001',0), +10002 => array(DEFAULT_ERROR_MESSAGE.'10002',0), +10003 => array(DEFAULT_ERROR_MESSAGE.'10003',0), +11001 => array(DEFAULT_ERROR_MESSAGE.'11001',0), +11002 => array(DEFAULT_ERROR_MESSAGE.'11002',0), +11003 => array(DEFAULT_ERROR_MESSAGE.'11003',0), +11004 => array(DEFAULT_ERROR_MESSAGE.'11004',0), +11005 => array(DEFAULT_ERROR_MESSAGE.'11005',0), +12001 => array(DEFAULT_ERROR_MESSAGE.'12001',0), +12002 => array(DEFAULT_ERROR_MESSAGE.'12002',0), +12003 => array(DEFAULT_ERROR_MESSAGE.'12003',0), +12004 => array(DEFAULT_ERROR_MESSAGE.'12004',0), +12005 => array(DEFAULT_ERROR_MESSAGE.'12005',0), +12006 => array(DEFAULT_ERROR_MESSAGE.'12006',0), +12007 => array(DEFAULT_ERROR_MESSAGE.'12007',0), +12008 => array(DEFAULT_ERROR_MESSAGE.'12008',0), +12009 => array(DEFAULT_ERROR_MESSAGE.'12009',0), +12010 => array(DEFAULT_ERROR_MESSAGE.'12010',0), +12011 => array(DEFAULT_ERROR_MESSAGE.'12011',0), +12012 => array(DEFAULT_ERROR_MESSAGE.'12012',0), +14001 => array(DEFAULT_ERROR_MESSAGE.'14001',0), +14002 => array(DEFAULT_ERROR_MESSAGE.'14002',0), +14003 => array(DEFAULT_ERROR_MESSAGE.'14003',0), +14004 => array(DEFAULT_ERROR_MESSAGE.'14004',0), +14005 => array(DEFAULT_ERROR_MESSAGE.'14005',0), +14006 => array(DEFAULT_ERROR_MESSAGE.'14006',0), +14007 => array(DEFAULT_ERROR_MESSAGE.'14007',0), +15001 => array(DEFAULT_ERROR_MESSAGE.'15001',0), +15002 => array(DEFAULT_ERROR_MESSAGE.'15002',0), +15003 => array(DEFAULT_ERROR_MESSAGE.'15003',0), +15004 => array(DEFAULT_ERROR_MESSAGE.'15004',0), +15005 => array(DEFAULT_ERROR_MESSAGE.'15005',0), +15006 => array(DEFAULT_ERROR_MESSAGE.'15006',0), +15007 => array(DEFAULT_ERROR_MESSAGE.'15007',0), +15008 => array(DEFAULT_ERROR_MESSAGE.'15008',0), +15009 => array(DEFAULT_ERROR_MESSAGE.'15009',0), +15010 => array(DEFAULT_ERROR_MESSAGE.'15010',0), +15011 => array(DEFAULT_ERROR_MESSAGE.'15011',0), +16001 => array(DEFAULT_ERROR_MESSAGE.'16001',0), +16002 => array(DEFAULT_ERROR_MESSAGE.'16002',0), +16003 => array(DEFAULT_ERROR_MESSAGE.'16003',0), +16004 => array(DEFAULT_ERROR_MESSAGE.'16004',0), +17001 => array(DEFAULT_ERROR_MESSAGE.'17001',0), +17002 => array(DEFAULT_ERROR_MESSAGE.'17002',0), +17004 => array(DEFAULT_ERROR_MESSAGE.'17004',0), +18001 => array(DEFAULT_ERROR_MESSAGE.'18001',0), +18002 => array(DEFAULT_ERROR_MESSAGE.'18002',0), +18003 => array(DEFAULT_ERROR_MESSAGE.'18003',0), +18004 => array(DEFAULT_ERROR_MESSAGE.'18004',0), +18005 => array(DEFAULT_ERROR_MESSAGE.'18005',0), +18006 => array(DEFAULT_ERROR_MESSAGE.'18006',0), +18007 => array(DEFAULT_ERROR_MESSAGE.'18007',0), +18008 => array(DEFAULT_ERROR_MESSAGE.'18008',0), +19001 => array(DEFAULT_ERROR_MESSAGE.'19001',0), +19002 => array(DEFAULT_ERROR_MESSAGE.'19002',0), +19003 => array(DEFAULT_ERROR_MESSAGE.'19003',0), +20001 => array(DEFAULT_ERROR_MESSAGE.'20001',0), +20002 => array(DEFAULT_ERROR_MESSAGE.'20002',0), +20003 => array(DEFAULT_ERROR_MESSAGE.'20003',0), +21001 => array(DEFAULT_ERROR_MESSAGE.'21001',0), +23001 => array(DEFAULT_ERROR_MESSAGE.'23001',0), +23002 => array(DEFAULT_ERROR_MESSAGE.'23002',0), +23003 => array(DEFAULT_ERROR_MESSAGE.'23003',0), +24001 => array(DEFAULT_ERROR_MESSAGE.'24001',0), +24002 => array(DEFAULT_ERROR_MESSAGE.'24002',0), +24003 => array(DEFAULT_ERROR_MESSAGE.'24003',0), +25001 => array(DEFAULT_ERROR_MESSAGE.'25001',0), +25002 => array(DEFAULT_ERROR_MESSAGE.'25002',0), +25003 => array(DEFAULT_ERROR_MESSAGE.'25003',0), +25004 => array(DEFAULT_ERROR_MESSAGE.'25004',0), +25005 => array(DEFAULT_ERROR_MESSAGE.'25005',0), +25006 => array(DEFAULT_ERROR_MESSAGE.'25006',0), +25007 => array(DEFAULT_ERROR_MESSAGE.'25007',0), +25008 => array(DEFAULT_ERROR_MESSAGE.'25008',0), +25009 => array(DEFAULT_ERROR_MESSAGE.'25009',0), +25010 => array(DEFAULT_ERROR_MESSAGE.'25010',0), +25011 => array(DEFAULT_ERROR_MESSAGE.'25011',0), +25012 => array(DEFAULT_ERROR_MESSAGE.'25012',0), +25013 => array(DEFAULT_ERROR_MESSAGE.'25013',0), +25014 => array(DEFAULT_ERROR_MESSAGE.'25014',0), +25015 => array(DEFAULT_ERROR_MESSAGE.'25015',0), +25016 => array(DEFAULT_ERROR_MESSAGE.'25016',0), +25017 => array(DEFAULT_ERROR_MESSAGE.'25017',0), +25018 => array(DEFAULT_ERROR_MESSAGE.'25018',0), +25019 => array(DEFAULT_ERROR_MESSAGE.'25019',0), +25020 => array(DEFAULT_ERROR_MESSAGE.'25020',0), +25021 => array(DEFAULT_ERROR_MESSAGE.'25021',0), +25022 => array(DEFAULT_ERROR_MESSAGE.'25022',0), +25023 => array(DEFAULT_ERROR_MESSAGE.'25023',0), +25024 => array(DEFAULT_ERROR_MESSAGE.'25024',0), +25025 => array(DEFAULT_ERROR_MESSAGE.'25025',0), +25026 => array(DEFAULT_ERROR_MESSAGE.'25026',0), +25027 => array(DEFAULT_ERROR_MESSAGE.'25027',0), +25028 => array(DEFAULT_ERROR_MESSAGE.'25028',0), +25029 => array(DEFAULT_ERROR_MESSAGE.'25029',0), +25030 => array(DEFAULT_ERROR_MESSAGE.'25030',0), +25031 => array(DEFAULT_ERROR_MESSAGE.'25031',0), +25032 => array(DEFAULT_ERROR_MESSAGE.'25032',0), +25033 => array(DEFAULT_ERROR_MESSAGE.'25033',0), +25034 => array(DEFAULT_ERROR_MESSAGE.'25034',0), +25035 => array(DEFAULT_ERROR_MESSAGE.'25035',0), +25036 => array(DEFAULT_ERROR_MESSAGE.'25036',0), +25037 => array(DEFAULT_ERROR_MESSAGE.'25037',0), +25038 => array(DEFAULT_ERROR_MESSAGE.'25038',0), +25039 => array(DEFAULT_ERROR_MESSAGE.'25039',0), +25040 => array(DEFAULT_ERROR_MESSAGE.'25040',0), +25041 => array(DEFAULT_ERROR_MESSAGE.'25041',0), +25042 => array(DEFAULT_ERROR_MESSAGE.'25042',0), +25043 => array(DEFAULT_ERROR_MESSAGE.'25043',0), +25044 => array(DEFAULT_ERROR_MESSAGE.'25044',0), +25045 => array(DEFAULT_ERROR_MESSAGE.'25045',0), +25046 => array(DEFAULT_ERROR_MESSAGE.'25046',0), +25047 => array(DEFAULT_ERROR_MESSAGE.'25047',0), +25048 => array(DEFAULT_ERROR_MESSAGE.'25048',0), +25049 => array(DEFAULT_ERROR_MESSAGE.'25049',0), +25050 => array(DEFAULT_ERROR_MESSAGE.'25050',0), +25051 => array(DEFAULT_ERROR_MESSAGE.'25051',0), +25052 => array(DEFAULT_ERROR_MESSAGE.'25052',0), +25053 => array(DEFAULT_ERROR_MESSAGE.'25053',0), +25054 => array(DEFAULT_ERROR_MESSAGE.'25054',0), +25055 => array(DEFAULT_ERROR_MESSAGE.'25055',0), +25056 => array(DEFAULT_ERROR_MESSAGE.'25056',0), +25057 => array(DEFAULT_ERROR_MESSAGE.'25057',0), +25058 => array(DEFAULT_ERROR_MESSAGE.'25058',0), +25059 => array(DEFAULT_ERROR_MESSAGE.'25059',0), +25060 => array(DEFAULT_ERROR_MESSAGE.'25060',0), +25061 => array(DEFAULT_ERROR_MESSAGE.'25061',0), +25062 => array(DEFAULT_ERROR_MESSAGE.'25062',0), +25063 => array(DEFAULT_ERROR_MESSAGE.'25063',0), +25064 => array(DEFAULT_ERROR_MESSAGE.'25064',0), +25065 => array(DEFAULT_ERROR_MESSAGE.'25065',0), +25066 => array(DEFAULT_ERROR_MESSAGE.'25066',0), +25067 => array(DEFAULT_ERROR_MESSAGE.'25067',0), +25068 => array(DEFAULT_ERROR_MESSAGE.'25068',0), +25069 => array(DEFAULT_ERROR_MESSAGE.'25069',0), +25070 => array(DEFAULT_ERROR_MESSAGE.'25070',0), +25071 => array(DEFAULT_ERROR_MESSAGE.'25071',0), +25072 => array(DEFAULT_ERROR_MESSAGE.'25072',0), +25073 => array(DEFAULT_ERROR_MESSAGE.'25073',0), +25074 => array(DEFAULT_ERROR_MESSAGE.'25074',0), +25075 => array(DEFAULT_ERROR_MESSAGE.'25075',0), +25077 => array(DEFAULT_ERROR_MESSAGE.'25077',0), +25078 => array(DEFAULT_ERROR_MESSAGE.'25078',0), +25079 => array(DEFAULT_ERROR_MESSAGE.'25079',0), +25080 => array(DEFAULT_ERROR_MESSAGE.'25080',0), +25081 => array(DEFAULT_ERROR_MESSAGE.'25081',0), +25082 => array(DEFAULT_ERROR_MESSAGE.'25082',0), +25083 => array(DEFAULT_ERROR_MESSAGE.'25083',0), +25084 => array(DEFAULT_ERROR_MESSAGE.'25084',0), +25085 => array(DEFAULT_ERROR_MESSAGE.'25085',0), +25086 => array(DEFAULT_ERROR_MESSAGE.'25086',0), +25087 => array(DEFAULT_ERROR_MESSAGE.'25087',0), +25088 => array(DEFAULT_ERROR_MESSAGE.'25088',0), +25089 => array(DEFAULT_ERROR_MESSAGE.'25089',0), +25090 => array(DEFAULT_ERROR_MESSAGE.'25090',0), +25091 => array(DEFAULT_ERROR_MESSAGE.'25091',0), +25092 => array(DEFAULT_ERROR_MESSAGE.'25092',0), +25093 => array(DEFAULT_ERROR_MESSAGE.'25093',0), +25094 => array(DEFAULT_ERROR_MESSAGE.'25094',0), +25095 => array(DEFAULT_ERROR_MESSAGE.'25095',0), +25096 => array(DEFAULT_ERROR_MESSAGE.'25096',0), +25097 => array(DEFAULT_ERROR_MESSAGE.'25097',0), +25098 => array(DEFAULT_ERROR_MESSAGE.'25098',0), +25099 => array(DEFAULT_ERROR_MESSAGE.'25099',0), +25100 => array(DEFAULT_ERROR_MESSAGE.'25100',0), +25101 => array(DEFAULT_ERROR_MESSAGE.'25101',0), +25102 => array(DEFAULT_ERROR_MESSAGE.'25102',0), +25103 => array(DEFAULT_ERROR_MESSAGE.'25103',0), +25104 => array(DEFAULT_ERROR_MESSAGE.'25104',0), +25105 => array(DEFAULT_ERROR_MESSAGE.'25105',0), +25106 => array(DEFAULT_ERROR_MESSAGE.'25106',0), +25107 => array(DEFAULT_ERROR_MESSAGE.'25107',0), +25108 => array(DEFAULT_ERROR_MESSAGE.'25108',0), +25109 => array(DEFAULT_ERROR_MESSAGE.'25109',0), +25110 => array(DEFAULT_ERROR_MESSAGE.'25110',0), +25111 => array(DEFAULT_ERROR_MESSAGE.'25111',0), +25112 => array(DEFAULT_ERROR_MESSAGE.'25112',0), +25113 => array(DEFAULT_ERROR_MESSAGE.'25113',0), +25114 => array(DEFAULT_ERROR_MESSAGE.'25114',0), +25115 => array(DEFAULT_ERROR_MESSAGE.'25115',0), +25116 => array(DEFAULT_ERROR_MESSAGE.'25116',0), +25117 => array(DEFAULT_ERROR_MESSAGE.'25117',0), +25118 => array(DEFAULT_ERROR_MESSAGE.'25118',0), +25119 => array(DEFAULT_ERROR_MESSAGE.'25119',0), +25120 => array(DEFAULT_ERROR_MESSAGE.'25120',0), +25121 => array(DEFAULT_ERROR_MESSAGE.'25121',0), +25122 => array(DEFAULT_ERROR_MESSAGE.'25122',0), +25123 => array(DEFAULT_ERROR_MESSAGE.'25123',0), +25124 => array(DEFAULT_ERROR_MESSAGE.'25124',0), +25125 => array(DEFAULT_ERROR_MESSAGE.'25125',0), +25126 => array(DEFAULT_ERROR_MESSAGE.'25126',0), +25127 => array(DEFAULT_ERROR_MESSAGE.'25127',0), +25128 => array(DEFAULT_ERROR_MESSAGE.'25128',0), +25129 => array(DEFAULT_ERROR_MESSAGE.'25129',0), +24003 => array(DEFAULT_ERROR_MESSAGE.'24003',0), +24004 => array(DEFAULT_ERROR_MESSAGE.'24004',0), +24005 => array(DEFAULT_ERROR_MESSAGE.'24005',0), +24006 => array(DEFAULT_ERROR_MESSAGE.'24006',0), +24007 => array(DEFAULT_ERROR_MESSAGE.'24007',0), +24008 => array(DEFAULT_ERROR_MESSAGE.'24008',0), +24009 => array(DEFAULT_ERROR_MESSAGE.'24009',0), +24010 => array(DEFAULT_ERROR_MESSAGE.'24010',0), +24011 => array(DEFAULT_ERROR_MESSAGE.'24011',0), +24012 => array(DEFAULT_ERROR_MESSAGE.'24012',0), +24013 => array(DEFAULT_ERROR_MESSAGE.'24013',0), +24014 => array(DEFAULT_ERROR_MESSAGE.'24014',0), +24015 => array(DEFAULT_ERROR_MESSAGE.'24015',0), +22001 => array(DEFAULT_ERROR_MESSAGE.'22001',0), +22002 => array(DEFAULT_ERROR_MESSAGE.'22002',0), +22004 => array(DEFAULT_ERROR_MESSAGE.'22004',0), +22005 => array(DEFAULT_ERROR_MESSAGE.'22005',0), +22006 => array(DEFAULT_ERROR_MESSAGE.'22006',0), +22007 => array(DEFAULT_ERROR_MESSAGE.'22007',0), +22008 => array(DEFAULT_ERROR_MESSAGE.'22008',0), +22009 => array(DEFAULT_ERROR_MESSAGE.'22009',0), +22010 => array(DEFAULT_ERROR_MESSAGE.'22010',0), +22011 => array(DEFAULT_ERROR_MESSAGE.'22011',0), +22012 => array(DEFAULT_ERROR_MESSAGE.'22012',0), +22013 => array(DEFAULT_ERROR_MESSAGE.'22013',0), +22014 => array(DEFAULT_ERROR_MESSAGE.'22014',0), +22015 => array(DEFAULT_ERROR_MESSAGE.'22015',0), +22016 => array(DEFAULT_ERROR_MESSAGE.'22016',0), +22017 => array(DEFAULT_ERROR_MESSAGE.'22017',0), +22018 => array(DEFAULT_ERROR_MESSAGE.'22018',0), +22019 => array(DEFAULT_ERROR_MESSAGE.'22019',0), +22020 => array(DEFAULT_ERROR_MESSAGE.'22020',0), +13001 => array(DEFAULT_ERROR_MESSAGE.'13001',0), +13002 => array(DEFAULT_ERROR_MESSAGE.'13002',0), +1001 => array(DEFAULT_ERROR_MESSAGE.'1001',0), +1002 => array(DEFAULT_ERROR_MESSAGE.'1002',0), +1003 => array(DEFAULT_ERROR_MESSAGE.'1003',0), +1004 => array(DEFAULT_ERROR_MESSAGE.'1004',0), +1005 => array(DEFAULT_ERROR_MESSAGE.'1005',0), +1006 => array(DEFAULT_ERROR_MESSAGE.'1006',0), +1007 => array(DEFAULT_ERROR_MESSAGE.'1007',0), +1008 => array(DEFAULT_ERROR_MESSAGE.'1008',0), +1009 => array(DEFAULT_ERROR_MESSAGE.'1009',0), +1010 => array(DEFAULT_ERROR_MESSAGE.'1010',0), +1011 => array(DEFAULT_ERROR_MESSAGE.'1011',0), +26001 => array(DEFAULT_ERROR_MESSAGE.'26001',0), +26002 => array(DEFAULT_ERROR_MESSAGE.'26002',0), +26003 => array(DEFAULT_ERROR_MESSAGE.'26003',0), +26004 => array(DEFAULT_ERROR_MESSAGE.'26004',0), +26005 => array(DEFAULT_ERROR_MESSAGE.'26005',0), +26006 => array(DEFAULT_ERROR_MESSAGE.'26006',0), +26007 => array(DEFAULT_ERROR_MESSAGE.'26007',0), +26008 => array(DEFAULT_ERROR_MESSAGE.'26008',0), +26009 => array(DEFAULT_ERROR_MESSAGE.'26009',0), +26010 => array(DEFAULT_ERROR_MESSAGE.'26010',0), +26011 => array(DEFAULT_ERROR_MESSAGE.'26011',0), +26012 => array(DEFAULT_ERROR_MESSAGE.'26012',0), +26013 => array(DEFAULT_ERROR_MESSAGE.'26013',0), +26014 => array(DEFAULT_ERROR_MESSAGE.'26014',0), +26015 => array(DEFAULT_ERROR_MESSAGE.'26015',0), +26016 => array(DEFAULT_ERROR_MESSAGE.'26016',0), + +27001 => array(DEFAULT_ERROR_MESSAGE.'27001',0), +27002 => array(DEFAULT_ERROR_MESSAGE.'27002',0), +27003 => array(DEFAULT_ERROR_MESSAGE.'27003',0), +27004 => array(DEFAULT_ERROR_MESSAGE.'27004',0), +27005 => array(DEFAULT_ERROR_MESSAGE.'27005',0), +27006 => array(DEFAULT_ERROR_MESSAGE.'27006',0), +27007 => array(DEFAULT_ERROR_MESSAGE.'27007',0), +27008 => array(DEFAULT_ERROR_MESSAGE.'27008',0), +27009 => array(DEFAULT_ERROR_MESSAGE.'27009',0), +27010 => array(DEFAULT_ERROR_MESSAGE.'27010',0), +27011 => array(DEFAULT_ERROR_MESSAGE.'27011',0), +27012 => array(DEFAULT_ERROR_MESSAGE.'27012',0), +27013 => array(DEFAULT_ERROR_MESSAGE.'27013',0), +27014 => array(DEFAULT_ERROR_MESSAGE.'27014',0), +27015 => array(DEFAULT_ERROR_MESSAGE.'27015',0), +); + +?> diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index e567081..bb35432 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -200,7 +200,7 @@ abstract class LogStream { * * @return integer Error stat */ - public abstract function GetCountSortedByField($szFieldId, $nFieldType); + public abstract function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit); /** diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index dac6810..6558b2a 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -506,7 +506,7 @@ class LogStreamDB extends LogStream { * * @return integer Error stat */ - public function GetCountSortedByField($szFieldId, $nFieldType) + public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit) { } diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index e2ba072..6b8eae8 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -581,7 +581,7 @@ class LogStreamDisk extends LogStream { * * @return integer Error stat */ - public function GetCountSortedByField($szFieldId, $nFieldType) + public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit) { // We loop through all loglines! this may take a while! $uID = UID_UNKNOWN; @@ -608,6 +608,9 @@ class LogStreamDisk extends LogStream { } } while ( ($ret = $this->ReadNext($uID, $logArray)) == SUCCESS ); + // Sort Array, so the highest count comes first! + array_multisort($aResult, SORT_NUMERIC, SORT_DESC); + // finally return result! return $aResult; } diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 7a613cb..57bccc8 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -516,7 +516,7 @@ class LogStreamPDO extends LogStream { * * @return integer Error stat */ - public function GetCountSortedByField($szFieldId, $nFieldType) + public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit) { } diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 423cb00..ed1e9ce 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -279,4 +279,12 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; +// Stats Site + $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; + $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; + $content['LN_STATS_GRAPH'] = "Graph"; + $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; + $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; + $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; + ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index c8b96d0..7aab70a 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -281,9 +281,11 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the // Stats Site $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; + $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; + $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 4300dac..ed1bc6f 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -284,4 +284,12 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; +// Stats Site + $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; + $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; + $content['LN_STATS_GRAPH'] = "Graph"; + $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; + $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; + $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; + ?> \ No newline at end of file diff --git a/src/templates/statistics.html b/src/templates/statistics.html index 8a0be3b..0556294 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -17,19 +17,25 @@
    {LN_MENU_STATISTICS}
    +

    - +
    - +
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSOURCE}
    -

    + +

    + + + +
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSYSLOGTAG}
    + +

    From b974bd145cbc6ef8e6283af423012f44b1c31126 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 9 Sep 2008 11:22:51 +0200 Subject: [PATCH 079/142] Implemented record limit into chartdata generation of the disk logstream --- src/chartgenerator.php | 4 ++-- src/classes/logstreamdisk.class.php | 29 ++++++++++++++++++++++++++++- src/lang/de/main.php | 3 +-- src/lang/en/main.php | 5 +++-- src/lang/pt_BR/main.php | 3 +-- src/templates/statistics.html | 4 ++-- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 804a244..31130a0 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -151,7 +151,7 @@ if ( !$content['error_occured'] ) // $graph->title->SetColor("darkred"); // Setup the tab title - $graph->tabtitle->Set('Messagecount sorted by "' . $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . '"'); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); @@ -207,7 +207,7 @@ if ( !$content['error_occured'] ) // Setup the tab title - $graph->tabtitle->Set('Messagecount sorted by "' . $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . '"'); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); // Setup the X and Y grid diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 6b8eae8..3cbe96e 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -583,11 +583,17 @@ class LogStreamDisk extends LogStream { */ public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit) { + global $content; + // We loop through all loglines! this may take a while! $uID = UID_UNKNOWN; $ret = $this->ReadNext($uID, $logArray); if ( $ret == SUCCESS ) { + // Initialize Array variable + $aResult = array(); + + // Loop through messages do { if ( isset($logArray[$szFieldId]) ) @@ -595,7 +601,20 @@ class LogStreamDisk extends LogStream { if ( isset($aResult[ $logArray[$szFieldId] ]) ) $aResult[ $logArray[$szFieldId] ]++; else - $aResult[ $logArray[$szFieldId] ] = 1; + { + // Initialize entry if we haven't exceeded the RecordLImit yet! + if ( count($aResult) < $nRecordLimit ) + $aResult[ $logArray[$szFieldId] ] = 1; + else + { + // Count record to others + if ( isset($aResult[ $content['LN_STATS_OTHERS'] ]) ) + $aResult[ $content['LN_STATS_OTHERS'] ]++; + else + $aResult[ $content['LN_STATS_OTHERS'] ] = 1; + } + + } /* if ( isset($aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT]) ) $aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT]++; @@ -611,6 +630,14 @@ class LogStreamDisk extends LogStream { // Sort Array, so the highest count comes first! array_multisort($aResult, SORT_NUMERIC, SORT_DESC); + if ( isset($aResult[ $content['LN_STATS_OTHERS'] ]) ) + { + // This will move the "Others" Element to the last position! + $arrEntryCopy = $aResult[ $content['LN_STATS_OTHERS'] ]; + unset($aResult[ $content['LN_STATS_OTHERS'] ]); + $aResult[ $content['LN_STATS_OTHERS'] ] = $arrEntryCopy; + } + // finally return result! return $aResult; } diff --git a/src/lang/de/main.php b/src/lang/de/main.php index ed1e9ce..e2d82e0 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -280,8 +280,7 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; // Stats Site - $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; - $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; + $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 7aab70a..e68ff17 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -280,8 +280,9 @@ $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; // Stats Site - $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; - $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; + $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; + $content['LN_STATS_OTHERS'] = "All Others"; +// $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index ed1bc6f..a44268b 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -285,8 +285,7 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; // Stats Site - $content['LN_STATS_COUNTBYSOURCE'] = "Messagecount by Source"; - $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; + $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; diff --git a/src/templates/statistics.html b/src/templates/statistics.html index 0556294..1d955b3 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -22,7 +22,7 @@ - +
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSOURCE}


    @@ -32,7 +32,7 @@ - +
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSYSLOGTAG}


    From 676b365a9022fef5e9b0784b59faa3eb9bc78fe1 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 9 Sep 2008 11:53:32 +0200 Subject: [PATCH 080/142] Minor tweaks and the Pie Chart --- src/chartgenerator.php | 17 ++++++++++++++--- src/classes/logstreamdisk.class.php | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 31130a0..2353d23 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -90,7 +90,12 @@ else } if ( isset($_GET['maxrecords']) ) +{ + // read and verify value $content['maxrecords'] = intval($_GET['maxrecords']); + if ( $content['maxrecords'] < 2 || $content['maxrecords'] > 100 ) + $content['maxrecords'] = 10; +} else $content['maxrecords'] = 10; // --- @@ -166,17 +171,23 @@ if ( !$content['error_occured'] ) // Create $p1 = new PiePlot3D($YchartData); $p1->SetLegends($XchartData); + $p1->SetEdge('#333333', 1); + $p1->SetTheme('earth'); /* "earth" * "pastel" * "sand" * "water" */ // $targ=array("pie3d_csimex1.php?v=1","pie3d_csimex1.php?v=2","pie3d_csimex1.php?v=3", // "pie3d_csimex1.php?v=4","pie3d_csimex1.php?v=5","pie3d_csimex1.php?v=6"); // $alts=array("val=%d","val=%d","val=%d","val=%d","val=%d","val=%d"); // $p1->SetCSIMTargets($targ,$alts); // Use absolute labels - $p1->SetLabelType(1); - $p1->value->SetFormat("%d"); + $p1->SetLabelType(0); + $p1->value->SetFormat("%d%%"); +// $p1->SetLabelType(1); +// $p1->value->SetFormat("%d"); // Move the pie slightly to the left - $p1->SetCenter(0.4,0.5); + $p1->SetLabelMargin(5); + $p1->SetCenter(0.4,0.7); + $p1->SetSize(0.3); $graph->Add($p1); } diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 3cbe96e..296896b 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -603,7 +603,7 @@ class LogStreamDisk extends LogStream { else { // Initialize entry if we haven't exceeded the RecordLImit yet! - if ( count($aResult) < $nRecordLimit ) + if ( count($aResult) < ($nRecordLimit-1) ) // -1 because the last entry will become all others $aResult[ $logArray[$szFieldId] ] = 1; else { From c24606b18af0475dc949571a77382db91f76c75e Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 9 Sep 2008 15:39:16 +0200 Subject: [PATCH 081/142] Made a lot of tweaks and enhancements to the chart generation --- src/chartgenerator.php | 165 ++++++++++++++++++++++++++++++++-------- src/lang/de/main.php | 2 + src/lang/en/main.php | 2 + src/lang/pt_BR/main.php | 2 + 4 files changed, 140 insertions(+), 31 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 2353d23..6b4f14e 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -145,7 +145,7 @@ if ( !$content['error_occured'] ) // Create Basic Image, and set basic properties! $graph = new PieGraph($content['chart_width'], $content['chart_width'], 'auto'); - $graph->SetMargin(60,20,30,30); // Adjust margin area + $graph->SetMargin(30,20,30,30); // Adjust margin area $graph->SetScale("textlin"); $graph->SetMarginColor('white'); $graph->SetBox(); // Box around plotarea @@ -156,13 +156,22 @@ if ( !$content['error_occured'] ) // $graph->title->SetColor("darkred"); // Setup the tab title - $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); - + $graph->tabtitle->SetPos('left'); + + // Set Graph footer + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); + $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); +// $graph->footer->right->SetColor("darkred"); // Setup font for axis - $graph->xaxis->SetFont(FF_VERDANA,FS_NORMAL,10); - $graph->yaxis->SetFont(FF_VERDANA,FS_NORMAL,10); + $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,10); + $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,10); // Show 0 label on Y-axis (default is not to show) $graph->yscale->ticks->SupressZeroLabel(false); @@ -178,16 +187,23 @@ if ( !$content['error_occured'] ) // $alts=array("val=%d","val=%d","val=%d","val=%d","val=%d","val=%d"); // $p1->SetCSIMTargets($targ,$alts); - // Use absolute labels + // Set label format $p1->SetLabelType(0); $p1->value->SetFormat("%d%%"); // $p1->SetLabelType(1); // $p1->value->SetFormat("%d"); - // Move the pie slightly to the left + // Set label properties + $p1->SetLabelPos(1.0); + $p1->SetSliceColors(array('#FFF584','#CBFF84','#FF6B9E','#FF9584','#EAFF84','#7BFF51','#51FFA6','#51FF52','#6BCFFF','#5170FF','#519CFF','#EAE3AD','#FFF184','#8584FF','#E698FF','#C384FF','#FF84EC','#FF98A3','#E5C285','#FFDA98' )); + $p1->value->SetFont(FF_ARIAL,FS_NORMAL); + $p1->value->SetColor("black"); + + // Adjust other Pie Properties $p1->SetLabelMargin(5); - $p1->SetCenter(0.4,0.7); + $p1->SetCenter(0.4,0.65); $p1->SetSize(0.3); + $p1->SetAngle(60); $graph->Add($p1); } @@ -199,27 +215,35 @@ if ( !$content['error_occured'] ) // Create Basic Image, and set basic properties! $graph = new Graph($content['chart_width'], $content['chart_width'], 'auto'); - $graph->SetMargin(60,20,30,30); // Adjust margin area + $graph->SetMargin(60,20,30,50); // Adjust margin area $graph->SetScale("textlin"); $graph->SetMarginColor('white'); $graph->SetBox(); // Box around plotarea - // Set up the title for the graph - // $graph->title->Set("Bar gradient (Left reflection)"); - // $graph->title->SetFont(FF_VERDANA,FS_NORMAL,12); - // $graph->title->SetColor("darkred"); + // Setup X-AXIS +// $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,10); + $graph->xaxis->SetTickLabels($XchartData); + $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->xaxis->SetLabelAngle(0); - // Setup font for axis - $graph->xaxis->SetFont(FF_VERDANA,FS_NORMAL,10); - $graph->yaxis->SetFont(FF_VERDANA,FS_NORMAL,10); + // Setup Y-AXIS + $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->yaxis->scale->SetGrace(10); // So the value is readable +// $graph->yaxis->SetLabelFormat('%d %%'); // Show 0 label on Y-axis (default is not to show) $graph->yscale->ticks->SupressZeroLabel(false); - // Setup the tab title - $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + $graph->tabtitle->SetPos('left'); + + // Set Graph footer + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); + $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); // Setup the X and Y grid $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); @@ -229,12 +253,7 @@ if ( !$content['error_occured'] ) $graph->xgrid->SetLineStyle('dashed'); $graph->xgrid->SetColor('gray'); - // Setup X-axis labels - $graph->xaxis->SetTickLabels($XchartData); - $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,8); - $graph->xaxis->SetLabelAngle(0); - - // Create a bar pot + // Create and Add bar pot $bplot = new BarPlot($YchartData); $bplot->SetWidth(0.6); $fcol='#440000'; @@ -242,24 +261,108 @@ if ( !$content['error_occured'] ) $bplot->SetFillGradient($fcol,$tcol,GRAD_LEFT_REFLECTION); $graph->Add($bplot); - // Create filled line plot + // Display value in bars + $bplot->value->Show(); + $bplot->value->SetFont(FF_ARIAL,FS_NORMAL,10); +// $bplot->value->SetAlign('left','center'); +// $bplot->value->SetColor("black","darkred"); + $bplot->value->SetFormat('%d'); + + +// TODO: Make Optional! + // Create and Add filled line plot $lplot = new LinePlot($YchartData); - $lplot->SetFillColor('skyblue@0.5'); + $lplot->SetFillColor('skyblue@0.7'); $lplot->SetColor('navy@0.7'); $lplot->SetBarCenter(); - $lplot->mark->SetType(MARK_SQUARE); - $lplot->mark->SetColor('blue@0.5'); + $lplot->mark->SetColor('blue@0.7'); $lplot->mark->SetFillColor('lightblue'); $lplot->mark->SetSize(6); - $graph->Add($lplot); } else if ( $content['chart_type'] == CHART_BARS_HORIZONTAL ) { - $content['error_occured'] = true; - $content['error_details'] = $content['LN_GEN_ERROR_INVALIDTYPE']; + // Include additional code filers for this chart! + include_once ($gl_root_path . "classes/jpgraph/jpgraph_bar.php"); + include_once ($gl_root_path . "classes/jpgraph/jpgraph_line.php"); + // Create Basic Image, and set basic properties! + $graph = new Graph($content['chart_width'], $content['chart_width'], 'auto'); +// $graph->SetMargin(60,20,30,50); + $graph->SetScale("textlin"); + $graph->Set90AndMargin(80,20,30,50); // Adjust margin area + $graph->SetMarginColor('white'); + $graph->SetBox(); // Box around plotarea + + // Setup X-AXIS +// $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,10); + $graph->xaxis->SetTickLabels($XchartData); + $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->xaxis->SetLabelAngle(0); +// $graph->xaxis->SetLabelAlign('center','top'); + $graph->xaxis->SetPos('min'); + + // Setup Y-AXIS + $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->yaxis->scale->SetGrace(10); // So the value is readable +// $graph->yaxis->SetLabelFormat('%d %%'); + $graph->yaxis->SetLabelAlign('center','top'); + $graph->yaxis->SetLabelFormat('%d'); + $graph->yaxis->SetLabelSide(SIDE_RIGHT); + $graph->yaxis->SetTickSide(SIDE_LEFT); +// $graph->yaxis->SetTitleSide(SIDE_RIGHT); +// $graph->yaxis->SetTitleMargin(35); + $graph->yaxis->SetPos('max'); + + // Show 0 label on Y-axis (default is not to show) + $graph->yscale->ticks->SupressZeroLabel(false); + + // Setup the tab title + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); + $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + $graph->tabtitle->SetPos('left'); + + // Set Graph footer + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); + $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); + + // Setup the X and Y grid + $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); + $graph->ygrid->SetLineStyle('dashed'); + $graph->ygrid->SetColor('gray'); + $graph->xgrid->Show(); + $graph->xgrid->SetLineStyle('dashed'); + $graph->xgrid->SetColor('gray'); + + // Create and Add bar pot + $bplot = new BarPlot($YchartData); + $bplot->SetWidth(0.6); + $fcol='#440000'; + $tcol='#FF9090'; + $bplot->SetFillGradient($fcol,$tcol,GRAD_LEFT_REFLECTION); + $graph->Add($bplot); + + // Display value in bars + $bplot->value->Show(); + $bplot->value->SetFont(FF_ARIAL,FS_NORMAL,10); +// $bplot->value->SetAlign('left','center'); +// $bplot->value->SetColor("black","darkred"); + $bplot->value->SetFormat('%d'); + +// TODO: Make Optional! + // Create and Add filled line plot + $lplot = new LinePlot($YchartData); + $lplot->SetFillColor('skyblue@0.7'); + $lplot->SetColor('navy@0.7'); + $lplot->SetBarCenter(); + $lplot->mark->SetType(MARK_SQUARE); + $lplot->mark->SetColor('blue@0.7'); + $lplot->mark->SetFillColor('lightblue'); + $lplot->mark->SetSize(6); + $graph->Add($lplot); } else { diff --git a/src/lang/de/main.php b/src/lang/de/main.php index e2d82e0..6f2e79f 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -282,6 +282,8 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; // Stats Site $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_GRAPH'] = "Graph"; + $content['LN_STATS_TOPRECORDS'] = "Maxrecords: %1"; + $content['LN_STATS_GENERATEDAT'] = "Generated at: %1"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index e68ff17..c5c4472 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -282,6 +282,8 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the // Stats Site $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_OTHERS'] = "All Others"; + $content['LN_STATS_TOPRECORDS'] = "Maxrecords: %1"; + $content['LN_STATS_GENERATEDAT'] = "Generated at: %1"; // $content['LN_STATS_COUNTBYSYSLOGTAG'] = "Messagecount by SyslogTag"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index a44268b..c607ed4 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -287,6 +287,8 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; // Stats Site $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_GRAPH'] = "Graph"; + $content['LN_STATS_TOPRECORDS'] = "Maxrecords: %1"; + $content['LN_STATS_GENERATEDAT'] = "Generated at: %1"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; From 1f5d518d8cd2f827a941aa8ae313d6b6690a5df3 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 9 Sep 2008 16:01:26 +0200 Subject: [PATCH 082/142] made some adjustments to the horizontal bar chart --- src/chartgenerator.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 6b4f14e..d0d1475 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -291,7 +291,7 @@ if ( !$content['error_occured'] ) $graph = new Graph($content['chart_width'], $content['chart_width'], 'auto'); // $graph->SetMargin(60,20,30,50); $graph->SetScale("textlin"); - $graph->Set90AndMargin(80,20,30,50); // Adjust margin area + $graph->Set90AndMargin(80,30,30,50); // Adjust margin area $graph->SetMarginColor('white'); $graph->SetBox(); // Box around plotarea @@ -302,11 +302,12 @@ if ( !$content['error_occured'] ) $graph->xaxis->SetLabelAngle(0); // $graph->xaxis->SetLabelAlign('center','top'); $graph->xaxis->SetPos('min'); + $graph->xaxis->SetLabelMargin(5); + $graph->xaxis->SetLabelAlign('right','center'); // Setup Y-AXIS $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,8); $graph->yaxis->scale->SetGrace(10); // So the value is readable -// $graph->yaxis->SetLabelFormat('%d %%'); $graph->yaxis->SetLabelAlign('center','top'); $graph->yaxis->SetLabelFormat('%d'); $graph->yaxis->SetLabelSide(SIDE_RIGHT); @@ -321,7 +322,8 @@ if ( !$content['error_occured'] ) // Setup the tab title $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); - $graph->tabtitle->SetPos('left'); + $graph->tabtitle->SetPos('right'); + $graph->tabtitle->SetTabAlign('right'); // Set Graph footer $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); From a450e686a6526b36514519ce3f119320821609c0 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Tue, 9 Sep 2008 16:41:43 +0200 Subject: [PATCH 083/142] Imnplemented GetCountSortedByField function into pdo and mysql logstream class --- src/chartgenerator.php | 2 +- src/classes/logstreamdb.class.php | 40 ++++++++++++++++++++++++ src/classes/logstreamdisk.class.php | 10 ------ src/classes/logstreampdo.class.php | 48 +++++++++++++++++++++++++++++ src/include/constants_errors.php | 1 + 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index d0d1475..977f744 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -307,7 +307,7 @@ if ( !$content['error_occured'] ) // Setup Y-AXIS $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,8); - $graph->yaxis->scale->SetGrace(10); // So the value is readable + $graph->yaxis->scale->SetGrace(20); // So the value is readable $graph->yaxis->SetLabelAlign('center','top'); $graph->yaxis->SetLabelFormat('%d'); $graph->yaxis->SetLabelSide(SIDE_RIGHT); diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 6558b2a..7d1e0c2 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -508,6 +508,46 @@ class LogStreamDB extends LogStream { */ public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit) { + global $content, $dbmapping; + + // Copy helper variables, this is just for better readability + $szTableType = $this->_logStreamConfigObj->DBTableType; + + if ( isset($dbmapping[$szTableType][$szFieldId]) ) + { + $myDBFieldName = $dbmapping[$szTableType][$szFieldId]; + + // Create SQL String now! + $szSql = "SELECT " . + $myDBFieldName . ", " . + "count(" . $myDBFieldName . ") as TotalCount " . + " FROM " . $this->_logStreamConfigObj->DBTableName . + " GROUP BY " . $myDBFieldName . + " ORDER BY TotalCount DESC" . + " LIMIT " . $nRecordLimit; + + + // Perform Database Query + $myquery = mysql_query($szSql, $this->_dbhandle); + if ( !$myquery ) + return ERROR_DB_QUERYFAILED; + + // Initialize Array variable + $aResult = array(); + + // read data records + while ($myRow = mysql_fetch_array($myquery, MYSQL_ASSOC)) + $aResult[ $myRow[$myDBFieldName] ] = $myRow['TotalCount']; +//print_r ($aResult); +//exit; + // return finished array + return $aResult; + } + else + { + // return error code, field mapping not found + return ERROR_DB_DBFIELDNOTFOUND; + } } diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 296896b..2c6c8ce 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -613,17 +613,7 @@ class LogStreamDisk extends LogStream { else $aResult[ $content['LN_STATS_OTHERS'] ] = 1; } - } - /* - if ( isset($aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT]) ) - $aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT]++; - else - { - $aResult[ $logArray[$szFieldId] ][CHARTDATA_NAME] = $logArray[$szFieldId]; - $aResult[ $logArray[$szFieldId] ][CHARTDATA_COUNT] = 1; - } - */ } } while ( ($ret = $this->ReadNext($uID, $logArray)) == SUCCESS ); diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 57bccc8..9dcc3fc 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -518,6 +518,54 @@ class LogStreamPDO extends LogStream { */ public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit) { + global $content, $dbmapping; + + // Copy helper variables, this is just for better readability + $szTableType = $this->_logStreamConfigObj->DBTableType; + + if ( isset($dbmapping[$szTableType][$szFieldId]) ) + { + $myDBFieldName = $dbmapping[$szTableType][$szFieldId]; + + // Create SQL String now! + $szSql = "SELECT " . + $myDBFieldName . ", " . + "count(" . $myDBFieldName . ") as TotalCount " . + " FROM " . $this->_logStreamConfigObj->DBTableName . + " GROUP BY " . $myDBFieldName . + " ORDER BY TotalCount DESC"; + // Append LIMIT in this case! + if ( $this->_logStreamConfigObj->DBType == DB_MYSQL || + $this->_logStreamConfigObj->DBType == DB_PGSQL ) + $szSql .= " LIMIT " . $nRecordLimit; + + // Perform Database Query + $this->_myDBQuery = $this->_dbhandle->query($szSql); + if ( !$this->_myDBQuery ) + return ERROR_DB_QUERYFAILED; + + if ( $this->_myDBQuery->rowCount() == 0 ) + return ERROR_NOMORERECORDS; + + // Initialize Array variable + $aResult = array(); + + // read data records + $iCount = 0; + while ( ($myRow = $this->_myDBQuery->fetch(PDO::FETCH_ASSOC)) && $iCount < $nRecordLimit) + { + $aResult[ $myRow[$myDBFieldName] ] = $myRow['TotalCount']; + $iCount++; + } + + // return finished array + return $aResult; + } + else + { + // return error code, field mapping not found + return ERROR_DB_DBFIELDNOTFOUND; + } } diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index 6323849..4cefa0a 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -58,6 +58,7 @@ define('ERROR_DB_NOPROPERTIES', 13); define('ERROR_DB_INVALIDDBMAPPING', 14); define('ERROR_DB_INVALIDDBDRIVER', 16); define('ERROR_DB_TABLENOTFOUND', 17); +define('ERROR_DB_DBFIELDNOTFOUND', 19); define('ERROR_MSG_NOMATCH', 18); From 9f695db85215c88a46e2ca4bb7559ba59ddf065d Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 12:23:38 +0200 Subject: [PATCH 084/142] Minor tweaks in chart generator --- src/chartgenerator.php | 10 +++++----- src/lang/de/main.php | 1 + src/lang/en/main.php | 1 + src/lang/pt_BR/main.php | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 977f744..6cb29b5 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -156,7 +156,7 @@ if ( !$content['error_occured'] ) // $graph->title->SetColor("darkred"); // Setup the tab title - $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); $graph->tabtitle->SetPos('left'); @@ -165,8 +165,8 @@ if ( !$content['error_occured'] ) $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); - $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); +// $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); +// $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); // $graph->footer->right->SetColor("darkred"); // Setup font for axis @@ -235,7 +235,7 @@ if ( !$content['error_occured'] ) $graph->yscale->ticks->SupressZeroLabel(false); // Setup the tab title - $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); $graph->tabtitle->SetPos('left'); @@ -320,7 +320,7 @@ if ( !$content['error_occured'] ) $graph->yscale->ticks->SupressZeroLabel(false); // Setup the tab title - $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_COUNTBY'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) . " - " . GetAndReplaceLangStr($content['LN_STATS_TOPRECORDS'], $content['maxrecords']) ); + $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); $graph->tabtitle->SetPos('right'); $graph->tabtitle->SetTabAlign('right'); diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 6f2e79f..7d8731c 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -280,6 +280,7 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; // Stats Site + $content['LN_STATS_CHARTTITLE'] = "Top %1 '%2' sorted by messagecount"; $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_STATS_TOPRECORDS'] = "Maxrecords: %1"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index c5c4472..8ba156a 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -280,6 +280,7 @@ $content['LN_CONVERT_PROCESS'] = "Conversion Progress:"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; // Stats Site + $content['LN_STATS_CHARTTITLE'] = "Top %1 '%2' sorted by messagecount"; $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_OTHERS'] = "All Others"; $content['LN_STATS_TOPRECORDS'] = "Maxrecords: %1"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index c607ed4..ca30cd9 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -285,6 +285,7 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the sources into the database, the SourceType '%1' is not supported by this phpLogCon Version."; // Stats Site + $content['LN_STATS_CHARTTITLE'] = "Top %1 '%2' sorted by messagecount"; $content['LN_STATS_COUNTBY'] = "Messagecount by '%1'"; $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_STATS_TOPRECORDS'] = "Maxrecords: %1"; From 3a7826b6f81dc58d4fc7805ae384030cbd068701 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 13:17:02 +0200 Subject: [PATCH 085/142] Implemented dynamic charts into the configuration system --- src/chartgenerator.php | 12 +++++ src/include/config.sample.php | 7 +++ src/include/constants_errors.php | 1 + src/include/db_template.txt | 18 ++++++++ src/include/db_update_v3.txt | 17 +++++++ src/include/functions_common.php | 7 ++- src/include/functions_config.php | 53 +++++++++++++++++++++ src/include/functions_db.php | 2 +- src/include/functions_installhelpers.php | 31 +++++++++++++ src/statistics.php | 59 ++++++++++++------------ src/templates/statistics.html | 33 ++++++------- 11 files changed, 189 insertions(+), 51 deletions(-) create mode 100644 src/include/db_update_v3.txt diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 6cb29b5..a876958 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -96,6 +96,18 @@ if ( isset($_GET['maxrecords']) ) if ( $content['maxrecords'] < 2 || $content['maxrecords'] > 100 ) $content['maxrecords'] = 10; } +else + $content['maxrecords'] = 10; + +if ( isset($_GET['showpercent']) ) +{ + // read and verify value + $content['showpercent'] = intval($_GET['showpercent']); + if ( $content['showpercent'] >= 1 ) + $content['showpercent'] = 1; + else + $content['showpercent'] = 0; +} else $content['maxrecords'] = 10; // --- diff --git a/src/include/config.sample.php b/src/include/config.sample.php index f2a678f..9ad76f6 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -98,6 +98,13 @@ $CFG['Search'][] = array ( "DisplayName" => "All messages from last 31 days", "S // $CFG['Search'][] = array ( "DisplayName" => "", "SearchQuery" => "" ); // --- +// --- Predefined Charts! +$CFG['Charts'][] = array ( "DisplayName" => "Top Hosts", "chart_type" => CHART_BARS_HORIZONTAL, "chart_width" => 400, "chart_field" => SYSLOG_HOST, "maxrecords" => 10, "showpercent" => 0 ); +$CFG['Charts'][] = array ( "DisplayName" => "SyslogTags", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_SYSLOGTAG, "maxrecords" => 10, "showpercent" => 0 ); +$CFG['Charts'][] = array ( "DisplayName" => "Severities", "chart_type" => CHART_BARS_VERTICAL, "chart_width" => 400, "chart_field" => SYSLOG_SEVERITY, "maxrecords" => 10, "showpercent" => 1 ); +$CFG['Charts'][] = array ( "DisplayName" => "Date", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_DATE, "maxrecords" => 10, "showpercent" => 0 ); +// --- + // --- Source Options /* Example for DiskType Source: $CFG['Sources']['Source1']['ID'] = "Source1"; diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index 4cefa0a..c80bfe4 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -61,6 +61,7 @@ define('ERROR_DB_TABLENOTFOUND', 17); define('ERROR_DB_DBFIELDNOTFOUND', 19); define('ERROR_MSG_NOMATCH', 18); +define('ERROR_CHARTS_NOTCONFIGURED', 20); ?> diff --git a/src/include/db_template.txt b/src/include/db_template.txt index 2eb633d..b5a9ea3 100644 --- a/src/include/db_template.txt +++ b/src/include/db_template.txt @@ -109,3 +109,21 @@ CREATE TABLE IF NOT EXISTS `logcon_views` ( `groupid` int(11) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Stores custom defined user views.' AUTO_INCREMENT=1 ; + +-- +-- Table structure for table `logcon_charts` +-- + +DROP TABLE IF EXISTS `logcon_charts`; +CREATE TABLE IF NOT EXISTS `logcon_charts` ( + `ID` int(11) NOT NULL auto_increment, + `DisplayName` varchar(255) NOT NULL, + `chart_type` int(11) NOT NULL, + `chart_width` int(11) NOT NULL, + `chart_field` varchar(255) NOT NULL, + `maxrecords` int(11) NOT NULL, + `showpercent` tinyint(1) NOT NULL, + `userid` int(11) default NULL, + `groupid` int(11) default NULL, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='This table contains all configured charts' AUTO_INCREMENT=1 ; diff --git a/src/include/db_update_v3.txt b/src/include/db_update_v3.txt new file mode 100644 index 0000000..3c7ccac --- /dev/null +++ b/src/include/db_update_v3.txt @@ -0,0 +1,17 @@ +-- New Database Structure Updates +CREATE TABLE IF NOT EXISTS `logcon_charts` ( + `ID` int(11) NOT NULL auto_increment, + `DisplayName` varchar(255) NOT NULL, + `chart_type` int(11) NOT NULL, + `chart_width` int(11) NOT NULL, + `chart_field` varchar(255) NOT NULL, + `maxrecords` int(11) NOT NULL, + `showpercent` tinyint(1) NOT NULL, + `userid` int(11) default NULL, + `groupid` int(11) default NULL, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='This table contains all configured charts' AUTO_INCREMENT=1 ; + +-- Insert data + +-- Updated Data diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 809654d..523c6f8 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -582,6 +582,9 @@ function InitConfigurationValues() // Load Configured Searches LoadSearchesFromDatabase(); + // Load Configured Charts + LoadChartsFromDatabase(); + // Load Configured Views LoadViewsFromDatabase(); @@ -1227,7 +1230,9 @@ function GetErrorMessage($errorCode) return $content['LN_ERROR_DB_INVALIDDBDRIVER']; case ERROR_DB_TABLENOTFOUND: return $content['LN_ERROR_DB_TABLENOTFOUND']; - + + case ERROR_CHARTS_NOTCONFIGURED: + return $content['LN_ERROR_CHARTS_NOTCONFIGURED']; default: return GetAndReplaceLangStr( $content['LN_ERROR_UNKNOWN'], $errorCode ); diff --git a/src/include/functions_config.php b/src/include/functions_config.php index fccf720..3d00a18 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -314,6 +314,7 @@ function InitPhpLogConConfigFile($bHandleMissing = true) define('DB_SOURCES', $tblPref . "sources"); define('DB_USERS', $tblPref . "users"); define('DB_VIEWS', $tblPref . "views"); + define('DB_CHARTS', $tblPref . "charts"); // Legacy support for old columns definition format! if ( isset($CFG['Columns']) && is_array($CFG['Columns']) ) @@ -394,6 +395,58 @@ function LoadSearchesFromDatabase() } } +/* +* Helper function to load configured Searches from the database +*/ +function LoadChartsFromDatabase() +{ + // Needed to make global + global $CFG, $content; + + // --- Create SQL Query + // Create Where for USERID + if ( isset($content['SESSION_LOGGEDIN']) && $content['SESSION_LOGGEDIN'] ) + $szWhereUser = " OR " . DB_CHARTS . ".userid = " . $content['SESSION_USERID'] . " "; + else + $szWhereUser = ""; + + if ( isset($content['SESSION_GROUPIDS']) ) + $szGroupWhere = " OR " . DB_CHARTS . ".groupid IN (" . $content['SESSION_GROUPIDS'] . ")"; + else + $szGroupWhere = ""; + $sqlquery = " SELECT " . + DB_CHARTS . ".ID, " . + DB_CHARTS . ".DisplayName, " . + DB_CHARTS . ".chart_type, " . + DB_CHARTS . ".chart_width, " . + DB_CHARTS . ".chart_field, " . + DB_CHARTS . ".maxrecords, " . + DB_CHARTS . ".showpercent, " . + DB_CHARTS . ".userid, " . + DB_CHARTS . ".groupid, " . + DB_USERS . ".username, " . + DB_GROUPS . ".groupname " . + " FROM " . DB_CHARTS . + " LEFT OUTER JOIN (" . DB_USERS . ") ON (" . DB_CHARTS . ".userid=" . DB_USERS . ".ID ) " . + " LEFT OUTER JOIN (" . DB_GROUPS . ") ON (" . DB_CHARTS . ".groupid=" . DB_GROUPS . ".ID ) " . + " WHERE (" . DB_CHARTS . ".userid IS NULL AND " . DB_CHARTS . ".groupid IS NULL) " . + $szWhereUser . + $szGroupWhere . + " ORDER BY " . DB_CHARTS . ".userid, " . DB_CHARTS . ".groupid, " . DB_CHARTS . ".DisplayName"; + // --- + + // Get Searches from DB now! + $result = DB_Query($sqlquery); + $myrows = DB_GetAllRows($result, true); + if ( isset($myrows ) && count($myrows) > 0 ) + { + // Overwrite Search Array with Database one + $CFG['Charts'] = $myrows; + $content['Charts'] = $myrows; + } +} + + function LoadViewsFromDatabase() { // Needed to make global diff --git a/src/include/functions_db.php b/src/include/functions_db.php index ea3839d..d9e0ce2 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -45,7 +45,7 @@ $errdesc = ""; $errno = 0; // --- Current Database Version, this is important for automated database Updates! -$content['database_internalversion'] = "2"; // Whenever incremented, a database upgrade is needed +$content['database_internalversion'] = "3"; // Whenever incremented, a database upgrade is needed $content['database_installedversion'] = "0"; // 0 is default which means Prior Versioning Database // --- diff --git a/src/include/functions_installhelpers.php b/src/include/functions_installhelpers.php index e0e5cfb..59768d2 100644 --- a/src/include/functions_installhelpers.php +++ b/src/include/functions_installhelpers.php @@ -87,6 +87,9 @@ function ConvertGeneralSettings() SaveGeneralSettingsIntoDB(); } +/* +* Convert Custom searches into DB +*/ function ConvertCustomSearches() { global $CFG, $content; @@ -102,6 +105,34 @@ function ConvertCustomSearches() } } +/* +* Convert Custom Charts into DB +*/ +function ConvertCustomCharts() +{ + global $CFG, $content; + + // Insert all searches into the DB! + foreach($CFG['Charts'] as $chartid => &$myChart) + { + // New Entry + $result = DB_Query("INSERT INTO " . DB_CHARTS . " (DisplayName, chart_type, chart_width, chart_field, maxrecords, showpercent) + VALUES ( + '" . PrepareValueForDB($myChart['DisplayName']) . "', + " . intval($mySearch['chart_type']) . ", + " . intval($mySearch['chart_width']) . ", + '" . PrepareValueForDB($mySearch['chart_field']) . "', + " . intval($mySearch['maxrecords']) . ", + " . intval($mySearch['showpercent']) . " + )"); + $myChart['DBID'] = DB_ReturnLastInsertID($result); + DB_FreeQuery($result); + } +} + +/* +* Convert Custom Views into DB +*/ function ConvertCustomViews() { global $CFG, $content; diff --git a/src/statistics.php b/src/statistics.php index 3de9ebc..522bbfc 100644 --- a/src/statistics.php +++ b/src/statistics.php @@ -53,43 +53,44 @@ InitFilterHelpers(); // Helpers for frontend filtering! // --- // --- BEGIN Custom Code -/*if ( isset($content['Sources'][$currentSourceID]) ) + +if ( isset($content['Charts']) ) { - // Obtain and get the Config Object - $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; + // This will enable to Stats View + $content['statsenabled'] = true; - // Create LogStream Object - $stream = $stream_config->LogStreamFactory($stream_config); - $res = $stream->Open( $content['AllColumns'], true ); - if ( $res == SUCCESS ) + // PreProcess Charts Array for display! + $i = 0; // Help counter! + foreach ($content['Charts'] as &$myChart ) { - // This will enable to Stats View - $content['statsenabled'] = "true"; - - - } - else - { - // This will disable to Stats View and show an error message - $content['statsenabled'] = "false"; - - // Set error code - $content['error_code'] = $ret; - - if ( $ret == ERROR_FILE_NOT_FOUND ) - $content['detailederror'] = $content['LN_ERROR_FILE_NOT_FOUND']; - else if ( $ret == ERROR_FILE_NOT_READABLE ) - $content['detailederror'] = $content['LN_ERROR_FILE_NOT_READABLE']; - else - $content['detailederror'] = $content['LN_ERROR_UNKNOWN']; + // --- Set CSS Class + if ( $i % 2 == 0 ) + { + $myChart['cssclass'] = "line1"; + $myChart['rowbegin'] = '
    '; + $myChart['rowend'] = ''; + $myChart['rowend'] = '
    + +
    - - - - +{rowend} +
    {LN_MENU_STATISTICS}
    -

    - - - + +{rowbegin} +

    +
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSOURCE}
    + +
    {DisplayName}
    + +
    -

    -
    -

    - - - - -
    {LN_STATS_GRAPH}: {LN_STATS_COUNTBYSYSLOGTAG}
    - -

    -
    + \ No newline at end of file From 1a32c0f3ebd815f97eae6fa9fbdfb5fa9e64c315 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 13:36:20 +0200 Subject: [PATCH 086/142] Enhanced statistics page and done some more tweaks to the charts --- src/lang/de/main.php | 1 + src/lang/en/main.php | 14 ++++++++++---- src/lang/pt_BR/main.php | 1 + src/statistics.php | 31 +++++++++++++++++++++++++++++++ src/templates/statistics.html | 26 +++++++++++++++++++++++--- src/themes/dark/main.css | 6 ++++++ src/themes/default/main.css | 6 ++++++ 7 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 7d8731c..26498aa 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -288,5 +288,6 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; + $content['LN_ERROR_CHARTS_NOTCONFIGURED'] = "There are no charts configured at all."; ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 8ba156a..cf10af8 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -289,9 +289,15 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the $content['LN_STATS_GRAPH'] = "Graph"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; - $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; - - - + $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type."; + $content['LN_ERROR_CHARTS_NOTCONFIGURED'] = "There are no charts configured at all."; + $content['LN_CHART_TYPE'] = "Chart type"; + $content['LN_CHART_WIDTH'] = "Chart width"; + $content['LN_CHART_FIELD'] = "Chart field"; + $content['LN_CHART_MAXRECORDS'] = "Top records count"; + $content['LN_CHART_SHOWPERCENT'] = "Show percentage data"; + $content['LN_CHART_TYPE_CAKE'] = "Cake (Pie)"; + $content['LN_CHART_TYPE_BARS_VERTICAL'] = "Bars vertical"; + $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; ?> \ No newline at end of file diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index ca30cd9..c36f9cb 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -293,5 +293,6 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_GEN_ERROR_INVALIDFIELD'] = "Invalid fieldname"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; + $content['LN_ERROR_CHARTS_NOTCONFIGURED'] = "There are no charts configured at all."; ?> \ No newline at end of file diff --git a/src/statistics.php b/src/statistics.php index 522bbfc..4fd6d39 100644 --- a/src/statistics.php +++ b/src/statistics.php @@ -63,6 +63,37 @@ if ( isset($content['Charts']) ) $i = 0; // Help counter! foreach ($content['Charts'] as &$myChart ) { + // --- Set display name for chart type + switch($myChart['chart_type']) + { + case CHART_CAKE: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_CAKE']; + break; + case CHART_BARS_VERTICAL: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_BARS_VERTICAL']; + break; + case CHART_BARS_HORIZONTAL: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_BARS_HORIZONTAL']; + break; + default: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_GEN_ERROR_INVALIDTYPE']; + break; + } + // --- + + // --- Set display name for chart field + if ( isset($myChart['chart_field']) && isset($content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]) ) + $myChart['CHART_FIELD_DISPLAYNAME'] = $content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]; + else + $myChart['CHART_FIELD_DISPLAYNAME'] = $myChart['chart_field']; + // --- + + // --- Set showpercent display + if ( $myChart['showpercent'] == 1 ) + $myChart['showpercent_display'] = "Yes"; + else + $myChart['showpercent_display'] = "No"; + // --- // --- Set CSS Class if ( $i % 2 == 0 ) diff --git a/src/templates/statistics.html b/src/templates/statistics.html index 67c6be0..c9e9d41 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -21,9 +21,29 @@ {rowbegin}

    - - - + + + + + {rowbegin}

    diff --git a/src/themes/dark/main.css b/src/themes/dark/main.css index c429c88..727710c 100644 --- a/src/themes/dark/main.css +++ b/src/themes/dark/main.css @@ -62,6 +62,16 @@ A.title:hover COLOR: #FF0A0C; TEXT-DECORATION: none; } +.titleSecond +{ + FONT: bold 10px Verdana, Arial, Helvetica, sans-serif; + BACKGROUND-COLOR: #301B22; + COLOR: #E5F377; + height: 18px; + text-align:center; + vertical-align:middle; +} + /* Context Link Classes */ a.contextlink:link,a.contextlink:active,a.contextlink:visited,a.contextlink diff --git a/src/themes/default/main.css b/src/themes/default/main.css index 587be8b..e284c73 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -81,6 +81,16 @@ A.title:hover COLOR: #982D00; TEXT-DECORATION: none; } +.titleSecond +{ + FONT: bold 10px Verdana, Arial, Helvetica, sans-serif; + BACKGROUND-COLOR: #E3D2AE; + COLOR: #1A3745; + height: 18px; + text-align:center; + vertical-align:middle; +} + /* Default Font Classes */ font From 24cd80089bcdc9c8ef484d1016581a8b52b84150 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 14:00:23 +0200 Subject: [PATCH 088/142] Added support for percentage display in pie chart --- src/chartgenerator.php | 16 +++++++++++----- src/templates/statistics.html | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index a876958..37863ef 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -109,7 +109,7 @@ if ( isset($_GET['showpercent']) ) $content['showpercent'] = 0; } else - $content['maxrecords'] = 10; + $content['showpercent'] = 0; // --- // --- BEGIN CREATE TITLE @@ -200,10 +200,16 @@ if ( !$content['error_occured'] ) // $p1->SetCSIMTargets($targ,$alts); // Set label format - $p1->SetLabelType(0); - $p1->value->SetFormat("%d%%"); -// $p1->SetLabelType(1); -// $p1->value->SetFormat("%d"); + if ( $content['showpercent'] == 1 ) + { + $p1->SetLabelType(0); + $p1->value->SetFormat("%d%%"); + } + else + { + $p1->SetLabelType(1); + $p1->value->SetFormat("%d"); + } // Set label properties $p1->SetLabelPos(1.0); diff --git a/src/templates/statistics.html b/src/templates/statistics.html index 0f410ea..35cf859 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -49,7 +49,7 @@
    {DisplayName}
    + + + + + + + + + + + + + + + + + + + + + + +
    {DisplayName}
    {LN_CHART_TYPE} {CHART_TYPE_DISPLAYNAME}
    {LN_CHART_FIELD} {CHART_FIELD_DISPLAYNAME}
    {LN_CHART_WIDTH} {chart_width}
    {LN_CHART_MAXRECORDS} {maxrecords}
    {LN_CHART_SHOWPERCENT} {showpercent_display}
    diff --git a/src/themes/dark/main.css b/src/themes/dark/main.css index b9ec9f2..c429c88 100644 --- a/src/themes/dark/main.css +++ b/src/themes/dark/main.css @@ -103,6 +103,12 @@ font border-color: #050A0F #050A0F #050A0F #050A0F; } +.table_with_border_light +{ + background-color:#1C2021; + border:1px #050A0F solid; +} + .with_border { text-indent:3px; diff --git a/src/themes/default/main.css b/src/themes/default/main.css index 082bd71..587be8b 100644 --- a/src/themes/default/main.css +++ b/src/themes/default/main.css @@ -104,6 +104,12 @@ font border-color: #CCCCCC #000000 #000000 #CCCCCC; } +.table_with_border_light +{ + background-color:#CCCCCC; + border:1px #AAAAAA solid; +} + .with_border { text-indent:3px; From cefdd9d6f1039006c559254b3fcd6aa4c08ff6d3 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 13:54:58 +0200 Subject: [PATCH 087/142] Minor tweaks to the css and titles of the chart page --- src/lang/en/main.php | 1 + src/templates/statistics.html | 5 +++++ src/themes/dark/main.css | 10 ++++++++++ src/themes/default/main.css | 10 ++++++++++ 4 files changed, 26 insertions(+) diff --git a/src/lang/en/main.php b/src/lang/en/main.php index cf10af8..e0bfd19 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -299,5 +299,6 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the $content['LN_CHART_TYPE_CAKE'] = "Cake (Pie)"; $content['LN_CHART_TYPE_BARS_VERTICAL'] = "Bars vertical"; $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; + $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; ?> \ No newline at end of file diff --git a/src/templates/statistics.html b/src/templates/statistics.html index c9e9d41..0f410ea 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -18,6 +18,11 @@
    {LN_MENU_STATISTICS}

    {LN_STATS_WARNINGDISPLAY}

     {showpercent_display}
    - +


    From efc27aed5fe48291df544f16ac0a44cf3e55e282 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 16:29:11 +0200 Subject: [PATCH 089/142] Added special handling for the date fields --- src/chartgenerator.php | 2 +- src/classes/logstreamdisk.class.php | 15 ++++++++++++--- src/lang/de/main.php | 9 +++++++++ src/lang/pt_BR/main.php | 9 +++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 37863ef..772fd9b 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -75,7 +75,7 @@ if ( isset($_GET['byfield']) ) if ( isset($fields[ $_GET['byfield'] ]) ) { $content['chart_field'] = $_GET['byfield']; - $content['chart_fieldtype'] = $fields[SYSLOG_UID]['FieldType']; + $content['chart_fieldtype'] = $fields[ $content['chart_field'] ]['FieldType']; } else { diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 2c6c8ce..409fd4f 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -598,13 +598,22 @@ class LogStreamDisk extends LogStream { { if ( isset($logArray[$szFieldId]) ) { - if ( isset($aResult[ $logArray[$szFieldId] ]) ) - $aResult[ $logArray[$szFieldId] ]++; + if ( $nFieldType == FILTER_TYPE_DATE ) + { + // Convert to FULL Day Date for now! + $myFieldData = date( "Y-m-d", $logArray[$szFieldId][EVTIME_TIMESTAMP] ); + } + else // Just copy the value! + $myFieldData = $logArray[$szFieldId]; + + + if ( isset($aResult[ $myFieldData ]) ) + $aResult[ $myFieldData ]++; else { // Initialize entry if we haven't exceeded the RecordLImit yet! if ( count($aResult) < ($nRecordLimit-1) ) // -1 because the last entry will become all others - $aResult[ $logArray[$szFieldId] ] = 1; + $aResult[ $myFieldData ] = 1; else { // Count record to others diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 26498aa..a0a9b52 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -289,5 +289,14 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; $content['LN_ERROR_CHARTS_NOTCONFIGURED'] = "There are no charts configured at all."; + $content['LN_CHART_TYPE'] = "Chart type"; + $content['LN_CHART_WIDTH'] = "Chart width"; + $content['LN_CHART_FIELD'] = "Chart field"; + $content['LN_CHART_MAXRECORDS'] = "Top records count"; + $content['LN_CHART_SHOWPERCENT'] = "Show percentage data"; + $content['LN_CHART_TYPE_CAKE'] = "Cake (Pie)"; + $content['LN_CHART_TYPE_BARS_VERTICAL'] = "Bars vertical"; + $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; + $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; ?> \ No newline at end of file diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index c36f9cb..360fe2c 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -294,5 +294,14 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_GEN_ERROR_MISSINGCHARTFIELD'] = "Missing fieldname"; $content['LN_GEN_ERROR_INVALIDTYPE'] = "Invalid or unknown chart type"; $content['LN_ERROR_CHARTS_NOTCONFIGURED'] = "There are no charts configured at all."; + $content['LN_CHART_TYPE'] = "Chart type"; + $content['LN_CHART_WIDTH'] = "Chart width"; + $content['LN_CHART_FIELD'] = "Chart field"; + $content['LN_CHART_MAXRECORDS'] = "Top records count"; + $content['LN_CHART_SHOWPERCENT'] = "Show percentage data"; + $content['LN_CHART_TYPE_CAKE'] = "Cake (Pie)"; + $content['LN_CHART_TYPE_BARS_VERTICAL'] = "Bars vertical"; + $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; + $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; ?> \ No newline at end of file From e3e640aba9ff11bea3c39813da65e299446fff11 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Sep 2008 17:37:17 +0200 Subject: [PATCH 090/142] Added special date handling for mysql and pdo logstream class as well --- src/classes/logstreamdb.class.php | 21 +++++++++++++++------ src/classes/logstreampdo.class.php | 25 ++++++++++++++++++++++--- src/include/config.sample.php | 4 ++-- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index 7d1e0c2..b16d6dc 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -515,17 +515,27 @@ class LogStreamDB extends LogStream { if ( isset($dbmapping[$szTableType][$szFieldId]) ) { + // Set DB Field name first! $myDBFieldName = $dbmapping[$szTableType][$szFieldId]; + $myDBQueryFieldName = $myDBFieldName; + $mySelectFieldName = $myDBFieldName; + + // Special handling for date fields + if ( $nFieldType == FILTER_TYPE_DATE ) + { + // Helper variable for the select statement + $mySelectFieldName = $mySelectFieldName . "Grouped"; + $myDBQueryFieldName = "DATE( " . $myDBFieldName . ") AS " . $mySelectFieldName ; + } // Create SQL String now! $szSql = "SELECT " . - $myDBFieldName . ", " . + $myDBQueryFieldName . ", " . "count(" . $myDBFieldName . ") as TotalCount " . " FROM " . $this->_logStreamConfigObj->DBTableName . - " GROUP BY " . $myDBFieldName . + " GROUP BY " . $mySelectFieldName . " ORDER BY TotalCount DESC" . " LIMIT " . $nRecordLimit; - // Perform Database Query $myquery = mysql_query($szSql, $this->_dbhandle); @@ -537,9 +547,8 @@ class LogStreamDB extends LogStream { // read data records while ($myRow = mysql_fetch_array($myquery, MYSQL_ASSOC)) - $aResult[ $myRow[$myDBFieldName] ] = $myRow['TotalCount']; -//print_r ($aResult); -//exit; + $aResult[ $myRow[$mySelectFieldName] ] = $myRow['TotalCount']; + // return finished array return $aResult; } diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 9dcc3fc..7ffe344 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -525,14 +525,33 @@ class LogStreamPDO extends LogStream { if ( isset($dbmapping[$szTableType][$szFieldId]) ) { + // Set DB Field name first! $myDBFieldName = $dbmapping[$szTableType][$szFieldId]; + $myDBQueryFieldName = $myDBFieldName; + $mySelectFieldName = $myDBFieldName; + + // Special handling for date fields + if ( $nFieldType == FILTER_TYPE_DATE ) + { + if ( $this->_logStreamConfigObj->DBType == DB_MYSQL || + $this->_logStreamConfigObj->DBType == DB_PGSQL ) + { + // Helper variable for the select statement + $mySelectFieldName = $mySelectFieldName . "Grouped"; + $myDBQueryFieldName = "DATE( " . $myDBFieldName . ") AS " . $mySelectFieldName ; + } + else if($this->_logStreamConfigObj->DBType == DB_MSSQL ) + { + // TODO FIND A WAY FOR MSSQL! + } + } // Create SQL String now! $szSql = "SELECT " . - $myDBFieldName . ", " . + $myDBQueryFieldName . ", " . "count(" . $myDBFieldName . ") as TotalCount " . " FROM " . $this->_logStreamConfigObj->DBTableName . - " GROUP BY " . $myDBFieldName . + " GROUP BY " . $mySelectFieldName . " ORDER BY TotalCount DESC"; // Append LIMIT in this case! if ( $this->_logStreamConfigObj->DBType == DB_MYSQL || @@ -554,7 +573,7 @@ class LogStreamPDO extends LogStream { $iCount = 0; while ( ($myRow = $this->_myDBQuery->fetch(PDO::FETCH_ASSOC)) && $iCount < $nRecordLimit) { - $aResult[ $myRow[$myDBFieldName] ] = $myRow['TotalCount']; + $aResult[ $myRow[$mySelectFieldName] ] = $myRow['TotalCount']; $iCount++; } diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 9ad76f6..7477c83 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -101,8 +101,8 @@ $CFG['Search'][] = array ( "DisplayName" => "All messages from last 31 days", "S // --- Predefined Charts! $CFG['Charts'][] = array ( "DisplayName" => "Top Hosts", "chart_type" => CHART_BARS_HORIZONTAL, "chart_width" => 400, "chart_field" => SYSLOG_HOST, "maxrecords" => 10, "showpercent" => 0 ); $CFG['Charts'][] = array ( "DisplayName" => "SyslogTags", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_SYSLOGTAG, "maxrecords" => 10, "showpercent" => 0 ); -$CFG['Charts'][] = array ( "DisplayName" => "Severities", "chart_type" => CHART_BARS_VERTICAL, "chart_width" => 400, "chart_field" => SYSLOG_SEVERITY, "maxrecords" => 10, "showpercent" => 1 ); -$CFG['Charts'][] = array ( "DisplayName" => "Date", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_DATE, "maxrecords" => 10, "showpercent" => 0 ); +$CFG['Charts'][] = array ( "DisplayName" => "Severity Occurences", "chart_type" => CHART_BARS_VERTICAL, "chart_width" => 400, "chart_field" => SYSLOG_SEVERITY, "maxrecords" => 10, "showpercent" => 1 ); +$CFG['Charts'][] = array ( "DisplayName" => "Usage by Day", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_DATE, "maxrecords" => 10, "showpercent" => 1 ); // --- // --- Source Options From fb242ca286d5e79d7a61a9eba568ede456609a5c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 10:50:38 +0200 Subject: [PATCH 091/142] Added sample charts into db upgrade script, and added option to disable charts --- src/include/config.sample.php | 8 +-- src/include/db_template.txt | 3 +- src/include/db_update_v3.txt | 5 ++ src/include/functions_config.php | 1 + src/include/functions_installhelpers.php | 13 ++-- src/statistics.php | 90 +++++++++++++----------- src/templates/statistics.html | 2 + 7 files changed, 69 insertions(+), 53 deletions(-) diff --git a/src/include/config.sample.php b/src/include/config.sample.php index 7477c83..d2191a2 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -99,10 +99,10 @@ $CFG['Search'][] = array ( "DisplayName" => "All messages from last 31 days", "S // --- // --- Predefined Charts! -$CFG['Charts'][] = array ( "DisplayName" => "Top Hosts", "chart_type" => CHART_BARS_HORIZONTAL, "chart_width" => 400, "chart_field" => SYSLOG_HOST, "maxrecords" => 10, "showpercent" => 0 ); -$CFG['Charts'][] = array ( "DisplayName" => "SyslogTags", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_SYSLOGTAG, "maxrecords" => 10, "showpercent" => 0 ); -$CFG['Charts'][] = array ( "DisplayName" => "Severity Occurences", "chart_type" => CHART_BARS_VERTICAL, "chart_width" => 400, "chart_field" => SYSLOG_SEVERITY, "maxrecords" => 10, "showpercent" => 1 ); -$CFG['Charts'][] = array ( "DisplayName" => "Usage by Day", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_DATE, "maxrecords" => 10, "showpercent" => 1 ); +$CFG['Charts'][] = array ( "DisplayName" => "Top Hosts", "chart_type" => CHART_BARS_HORIZONTAL, "chart_width" => 400, "chart_field" => SYSLOG_HOST, "maxrecords" => 10, "showpercent" => 0, "chart_enabled" => 1 ); +$CFG['Charts'][] = array ( "DisplayName" => "SyslogTags", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_SYSLOGTAG, "maxrecords" => 10, "showpercent" => 0, "chart_enabled" => 1 ); +$CFG['Charts'][] = array ( "DisplayName" => "Severity Occurences", "chart_type" => CHART_BARS_VERTICAL, "chart_width" => 400, "chart_field" => SYSLOG_SEVERITY, "maxrecords" => 10, "showpercent" => 1, "chart_enabled" => 1 ); +$CFG['Charts'][] = array ( "DisplayName" => "Usage by Day", "chart_type" => CHART_CAKE, "chart_width" => 400, "chart_field" => SYSLOG_DATE, "maxrecords" => 10, "showpercent" => 1, "chart_enabled" => 1 ); // --- // --- Source Options diff --git a/src/include/db_template.txt b/src/include/db_template.txt index b5a9ea3..654615f 100644 --- a/src/include/db_template.txt +++ b/src/include/db_template.txt @@ -114,10 +114,10 @@ CREATE TABLE IF NOT EXISTS `logcon_views` ( -- Table structure for table `logcon_charts` -- -DROP TABLE IF EXISTS `logcon_charts`; CREATE TABLE IF NOT EXISTS `logcon_charts` ( `ID` int(11) NOT NULL auto_increment, `DisplayName` varchar(255) NOT NULL, + `chart_enabled` tinyint(1) NOT NULL default '1', `chart_type` int(11) NOT NULL, `chart_width` int(11) NOT NULL, `chart_field` varchar(255) NOT NULL, @@ -127,3 +127,4 @@ CREATE TABLE IF NOT EXISTS `logcon_charts` ( `groupid` int(11) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='This table contains all configured charts' AUTO_INCREMENT=1 ; + diff --git a/src/include/db_update_v3.txt b/src/include/db_update_v3.txt index 3c7ccac..5c2df9e 100644 --- a/src/include/db_update_v3.txt +++ b/src/include/db_update_v3.txt @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS `logcon_charts` ( `ID` int(11) NOT NULL auto_increment, `DisplayName` varchar(255) NOT NULL, + `chart_enabled` tinyint(1) NOT NULL default '1', `chart_type` int(11) NOT NULL, `chart_width` int(11) NOT NULL, `chart_field` varchar(255) NOT NULL, @@ -13,5 +14,9 @@ CREATE TABLE IF NOT EXISTS `logcon_charts` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='This table contains all configured charts' AUTO_INCREMENT=1 ; -- Insert data +INSERT INTO `logcon_charts` (`ID`, `DisplayName`, `chart_enabled`, `chart_type`, `chart_width`, `chart_field`, `maxrecords`, `showpercent`, `userid`, `groupid`) VALUES (1, 'Top Hosts', 1, 3, 400, 'FROMHOST', 10, 0, NULL, NULL); +INSERT INTO `logcon_charts` (`ID`, `DisplayName`, `chart_enabled`, `chart_type`, `chart_width`, `chart_field`, `maxrecords`, `showpercent`, `userid`, `groupid`) VALUES (2, 'SyslogTags', 1, 1, 400, 'syslogtag', 10, 0, NULL, NULL); +INSERT INTO `logcon_charts` (`ID`, `DisplayName`, `chart_enabled`, `chart_type`, `chart_width`, `chart_field`, `maxrecords`, `showpercent`, `userid`, `groupid`) VALUES (3, 'Severity Occurences', 1, 2, 400, 'syslogseverity', 10, 1, NULL, NULL); +INSERT INTO `logcon_charts` (`ID`, `DisplayName`, `chart_enabled`, `chart_type`, `chart_width`, `chart_field`, `maxrecords`, `showpercent`, `userid`, `groupid`) VALUES (4, 'Usage by Day', 1, 1, 400, 'timereported', 10, 1, NULL, NULL); -- Updated Data diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 3d00a18..25386ee 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -417,6 +417,7 @@ function LoadChartsFromDatabase() $sqlquery = " SELECT " . DB_CHARTS . ".ID, " . DB_CHARTS . ".DisplayName, " . + DB_CHARTS . ".chart_enabled, " . DB_CHARTS . ".chart_type, " . DB_CHARTS . ".chart_width, " . DB_CHARTS . ".chart_field, " . diff --git a/src/include/functions_installhelpers.php b/src/include/functions_installhelpers.php index 59768d2..5a0882c 100644 --- a/src/include/functions_installhelpers.php +++ b/src/include/functions_installhelpers.php @@ -116,14 +116,15 @@ function ConvertCustomCharts() foreach($CFG['Charts'] as $chartid => &$myChart) { // New Entry - $result = DB_Query("INSERT INTO " . DB_CHARTS . " (DisplayName, chart_type, chart_width, chart_field, maxrecords, showpercent) + $result = DB_Query("INSERT INTO " . DB_CHARTS . " (DisplayName, chart_enabled, chart_type, chart_width, chart_field, maxrecords, showpercent) VALUES ( '" . PrepareValueForDB($myChart['DisplayName']) . "', - " . intval($mySearch['chart_type']) . ", - " . intval($mySearch['chart_width']) . ", - '" . PrepareValueForDB($mySearch['chart_field']) . "', - " . intval($mySearch['maxrecords']) . ", - " . intval($mySearch['showpercent']) . " + " . intval($myChart['chart_enabled']) . ", + " . intval($myChart['chart_type']) . ", + " . intval($myChart['chart_width']) . ", + '" . PrepareValueForDB($myChart['chart_field']) . "', + " . intval($myChart['maxrecords']) . ", + " . intval($myChart['showpercent']) . " )"); $myChart['DBID'] = DB_ReturnLastInsertID($result); DB_FreeQuery($result); diff --git a/src/statistics.php b/src/statistics.php index 4fd6d39..0277423 100644 --- a/src/statistics.php +++ b/src/statistics.php @@ -63,53 +63,57 @@ if ( isset($content['Charts']) ) $i = 0; // Help counter! foreach ($content['Charts'] as &$myChart ) { - // --- Set display name for chart type - switch($myChart['chart_type']) + // Only process if chart is enabled + if ( isset($myChart['chart_enabled']) && $myChart['chart_enabled'] == 1 ) { - case CHART_CAKE: - $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_CAKE']; - break; - case CHART_BARS_VERTICAL: - $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_BARS_VERTICAL']; - break; - case CHART_BARS_HORIZONTAL: - $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_BARS_HORIZONTAL']; - break; - default: - $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_GEN_ERROR_INVALIDTYPE']; - break; - } - // --- + // --- Set display name for chart type + switch($myChart['chart_type']) + { + case CHART_CAKE: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_CAKE']; + break; + case CHART_BARS_VERTICAL: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_BARS_VERTICAL']; + break; + case CHART_BARS_HORIZONTAL: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_CHART_TYPE_BARS_HORIZONTAL']; + break; + default: + $myChart['CHART_TYPE_DISPLAYNAME'] = $content['LN_GEN_ERROR_INVALIDTYPE']; + break; + } + // --- - // --- Set display name for chart field - if ( isset($myChart['chart_field']) && isset($content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]) ) - $myChart['CHART_FIELD_DISPLAYNAME'] = $content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]; - else - $myChart['CHART_FIELD_DISPLAYNAME'] = $myChart['chart_field']; - // --- + // --- Set display name for chart field + if ( isset($myChart['chart_field']) && isset($content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]) ) + $myChart['CHART_FIELD_DISPLAYNAME'] = $content[ $fields[$myChart['chart_field']]['FieldCaptionID'] ]; + else + $myChart['CHART_FIELD_DISPLAYNAME'] = $myChart['chart_field']; + // --- - // --- Set showpercent display - if ( $myChart['showpercent'] == 1 ) - $myChart['showpercent_display'] = "Yes"; - else - $myChart['showpercent_display'] = "No"; - // --- + // --- Set showpercent display + if ( $myChart['showpercent'] == 1 ) + $myChart['showpercent_display'] = "Yes"; + else + $myChart['showpercent_display'] = "No"; + // --- - // --- Set CSS Class - if ( $i % 2 == 0 ) - { - $myChart['cssclass'] = "line1"; - $myChart['rowbegin'] = '
    '; - $myChart['rowend'] = '
    '; + $myChart['rowend'] = ''; + $myChart['rowend'] = '
    '; - $myChart['rowend'] = '
    @@ -54,6 +55,7 @@


    {rowend} +
    From ccd5a640c3967d5380e4180ea42b5007c00bea4d Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 12:33:04 +0200 Subject: [PATCH 092/142] Started implementing charts admin --- src/admin/charts.php | 662 +++++++++++++++++++++++++ src/images/icons/column-chart-hori.png | Bin 0 -> 984 bytes src/images/icons/column-chart.png | Bin 0 -> 907 bytes src/images/icons/pie-chart.png | Bin 0 -> 880 bytes src/include/functions_common.php | 3 + src/lang/de/admin.php | 1 + src/lang/en/admin.php | 6 + src/lang/pt_BR/admin.php | 1 + src/templates/admin/admin_charts.html | 250 ++++++++++ src/templates/admin/admin_menu.html | 1 + 10 files changed, 924 insertions(+) create mode 100644 src/admin/charts.php create mode 100644 src/images/icons/column-chart-hori.png create mode 100644 src/images/icons/column-chart.png create mode 100644 src/images/icons/pie-chart.png create mode 100644 src/templates/admin/admin_charts.html diff --git a/src/admin/charts.php b/src/admin/charts.php new file mode 100644 index 0000000..5ebe5ab --- /dev/null +++ b/src/admin/charts.php @@ -0,0 +1,662 @@ + Helps administrating phplogcon charts & graphics + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './../'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +// Set PAGE to be ADMINPAGE! +define('IS_ADMINPAGE', true); +$content['IS_ADMINPAGE'] = true; +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! + +// Init admin langauge file now! +IncludeLanguageFile( $gl_root_path . '/lang/' . $LANG . '/admin.php' ); +// --- + +// --- BEGIN Custom Code + +if ( isset($_GET['op']) ) +{ + if ($_GET['op'] == "add") + { + // Set Mode to add + $content['ISEDITORNEWSOURCE'] = "true"; + $content['SOURCE_FORMACTION'] = "addnewsource"; + $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_ADD']; + + //PreInit these values + $content['Name'] = ""; + $content['SourceType'] = SOURCE_DISK; + CreateSourceTypesList($content['SourceType']); + $content['MsgParserList'] = ""; + $content['MsgNormalize'] = 0; + $content['CHECKED_ISNORMALIZEMSG'] = ""; + + // Init View List! + $content['SourceViewID'] = 'SYSLOG'; + $content['VIEWS'] = $content['Views']; + foreach ( $content['VIEWS'] as $myView ) + { + if ( $myView['ID'] == $content['SourceViewID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + } + + // SOURCE_DISK specific + $content['SourceLogLineType'] = ""; + CreateLogLineTypesList($content['SourceLogLineType']); + $content['SourceDiskFile'] = "/var/log/syslog"; + + // SOURCE_DB specific + $content['SourceDBType'] = DB_MYSQL; + CreateDBTypesList($content['SourceDBType']); + $content['SourceDBName'] = "phplogcon"; + $content['SourceDBTableType'] = "monitorware"; + $content['SourceDBServer'] = "localhost"; + $content['SourceDBTableName'] = "systemevents"; + $content['SourceDBUser'] = "user"; + $content['SourceDBPassword'] = ""; + $content['SourceDBEnableRowCounting'] = "false"; + $content['SourceDBEnableRowCounting_true'] = ""; + $content['SourceDBEnableRowCounting_false'] = "checked"; + + // General stuff + $content['userid'] = null; + $content['CHECKED_ISUSERONLY'] = ""; + $content['SOURCEID'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + $content['ISGROUPSAVAILABLE'] = true; + else + $content['ISGROUPSAVAILABLE'] = false; + } + else if ($_GET['op'] == "edit") + { + // Set Mode to edit + $content['ISEDITORNEWSOURCE'] = "true"; + $content['SOURCE_FORMACTION'] = "editsource"; + $content['SOURCE_SENDBUTTON'] = $content['LN_SOURCES_EDIT']; + + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); + + // Check if exists + if ( is_numeric($content['SOURCEID']) && isset($content['Sources'][ $content['SOURCEID'] ]) ) + { + // Get Source reference + $mysource = $content['Sources'][ $content['SOURCEID'] ]; + + // Copy basic properties + $content['Name'] = $mysource['Name']; + $content['SourceType'] = $mysource['SourceType']; + CreateSourceTypesList($content['SourceType']); + $content['MsgParserList'] = $mysource['MsgParserList']; + $content['MsgNormalize'] = $mysource['MsgNormalize']; + if ( $mysource['MsgNormalize'] == 1 ) + $content['CHECKED_ISNORMALIZEMSG'] = "checked"; + else + $content['CHECKED_ISNORMALIZEMSG'] = ""; + + // Init View List! + $content['SourceViewID'] = $mysource['ViewID']; + $content['VIEWS'] = $content['Views']; + foreach ( $content['VIEWS'] as $myView ) + { + if ( $myView['ID'] == $content['SourceViewID'] ) + $content['VIEWS'][ $myView['ID'] ]['selected'] = "selected"; + else + $content['VIEWS'][ $myView['ID'] ]['selected'] = ""; + } + + // SOURCE_DISK specific + $content['SourceLogLineType'] = $mysource['LogLineType']; + CreateLogLineTypesList($content['SourceLogLineType']); + $content['SourceDiskFile'] = $mysource['DiskFile']; + + // SOURCE_DB specific + $content['SourceDBType'] = $mysource['DBType']; + CreateDBTypesList($content['SourceDBType']); + $content['SourceDBName'] = $mysource['DBName']; + $content['SourceDBTableType'] = $mysource['DBTableType']; + $content['SourceDBServer'] = $mysource['DBServer']; + $content['SourceDBTableName'] = $mysource['DBTableName']; + $content['SourceDBUser'] = $mysource['DBUser']; + $content['SourceDBPassword'] = $mysource['DBPassword']; + $content['SourceDBEnableRowCounting'] = $mysource['DBEnableRowCounting']; + if ( $content['SourceDBEnableRowCounting'] == 1 ) + { + $content['SourceDBEnableRowCounting_true'] = "checked"; + $content['SourceDBEnableRowCounting_false'] = ""; + } + else + { + $content['SourceDBEnableRowCounting_true'] = ""; + $content['SourceDBEnableRowCounting_false'] = "checked"; + } + + if ( $mysource['userid'] != null ) + $content['CHECKED_ISUSERONLY'] = "checked"; + else + $content['CHECKED_ISUSERONLY'] = ""; + + // --- Check if groups are available + $content['SUBGROUPS'] = GetGroupsForSelectfield(); + if ( is_array($content['SUBGROUPS']) ) + { + // Process All Groups + for($i = 0; $i < count($content['SUBGROUPS']); $i++) + { + if ( $mysource['groupid'] != null && $content['SUBGROUPS'][$i]['mygroupid'] == $mysource['groupid'] ) + $content['SUBGROUPS'][$i]['group_selected'] = "selected"; + else + $content['SUBGROUPS'][$i]['group_selected'] = ""; + } + + // Enable Group Selection + $content['ISGROUPSAVAILABLE'] = true; + } + else + $content['ISGROUPSAVAILABLE'] = false; + // --- + } + else + { + $content['ISEDITORNEWSOURCE'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; + } + } + else + { + $content['ISEDITORNEWSEARCH'] = false; + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SEARCH_ERROR_INVALIDID']; + } + } + else if ($_GET['op'] == "delete") + { + if ( isset($_GET['id']) ) + { + //PreInit these values + $content['SOURCEID'] = DB_RemoveBadChars($_GET['id']); + + // Get UserInfo + $result = DB_Query("SELECT Name FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['Name']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); + } + + // --- Ask for deletion first! + if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) + { + // This will print an additional secure check which the user needs to confirm and exit the script execution. + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_SOURCES_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + } + // --- + + // do the delete! + $result = DB_Query( "DELETE FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID'] ); + if ($result == FALSE) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_DELSOURCE'], $content['SOURCEID'] ); + } + else + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_HASBEENDEL'], $myrow['Name'] ) , "sources.php" ); + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = $content['LN_SOURCES_ERROR_INVALIDORNOTFOUNDID']; + } + } +} + +if ( isset($_POST['op']) ) +{ + // Read parameters first! + if ( isset($_POST['id']) ) { $content['SOURCEID'] = intval(DB_RemoveBadChars($_POST['id'])); } else {$content['SOURCEID'] = -1; } + if ( isset($_POST['Name']) ) { $content['Name'] = DB_RemoveBadChars($_POST['Name']); } else {$content['Name'] = ""; } + if ( isset($_POST['SourceType']) ) { $content['SourceType'] = DB_RemoveBadChars($_POST['SourceType']); } + if ( isset($_POST['MsgParserList']) ) { $content['MsgParserList'] = DB_RemoveBadChars($_POST['MsgParserList']); } + if ( isset($_POST['MsgNormalize']) ) { $content['MsgNormalize'] = intval(DB_RemoveBadChars($_POST['MsgNormalize'])); } else {$content['MsgNormalize'] = 0; } + if ( isset($_POST['SourceViewID']) ) { $content['SourceViewID'] = DB_RemoveBadChars($_POST['SourceViewID']); } + + if ( isset($content['SourceType']) ) + { + // Disk Params + if ( $content['SourceType'] == SOURCE_DISK ) + { + if ( isset($_POST['SourceLogLineType']) ) { $content['SourceLogLineType'] = DB_RemoveBadChars($_POST['SourceLogLineType']); } + if ( isset($_POST['SourceDiskFile']) ) { $content['SourceDiskFile'] = DB_RemoveBadChars($_POST['SourceDiskFile']); } + } + // DB Params + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + if ( isset($_POST['SourceDBType']) ) { $content['SourceDBType'] = DB_RemoveBadChars($_POST['SourceDBType']); } + if ( isset($_POST['SourceDBName']) ) { $content['SourceDBName'] = DB_RemoveBadChars($_POST['SourceDBName']); } + if ( isset($_POST['SourceDBTableType']) ) { $content['SourceDBTableType'] = DB_RemoveBadChars($_POST['SourceDBTableType']); } + if ( isset($_POST['SourceDBServer']) ) { $content['SourceDBServer'] = DB_RemoveBadChars($_POST['SourceDBServer']); } + if ( isset($_POST['SourceDBTableName']) ) { $content['SourceDBTableName'] = DB_RemoveBadChars($_POST['SourceDBTableName']); } + if ( isset($_POST['SourceDBUser']) ) { $content['SourceDBUser'] = DB_RemoveBadChars($_POST['SourceDBUser']); } + if ( isset($_POST['SourceDBPassword']) ) { $content['SourceDBPassword'] = DB_RemoveBadChars($_POST['SourceDBPassword']); } else {$content['SourceDBPassword'] = ""; } + if ( isset($_POST['SourceDBEnableRowCounting']) ) { $content['SourceDBEnableRowCounting'] = DB_RemoveBadChars($_POST['SourceDBEnableRowCounting']); } + // Extra Check for this property + if ( $content['SourceDBEnableRowCounting'] != "true" ) + $content['SourceDBEnableRowCounting'] = "false"; + } + } + + // User & Group handeled specially + if ( isset ($_POST['isuseronly']) ) + { + $content['userid'] = $content['SESSION_USERID']; + $content['groupid'] = "null"; // Either user or group not both! + } + else + { + $content['userid'] = "null"; + if ( isset ($_POST['groupid']) && $_POST['groupid'] != -1 ) + $content['groupid'] = intval($_POST['groupid']); + else + $content['groupid'] = "null"; + } + + // --- Check mandotary values + if ( $content['Name'] == "" ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_NAMEOFTHESOURCE'] ); + } + else if ( !isset($content['SourceType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SOURCETYPE'] ); + } + else if ( !isset($content['SourceViewID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_VIEW'] ); + } + else + { + // Disk Params + if ( $content['SourceType'] == SOURCE_DISK ) + { + if ( !isset($content['SourceLogLineType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_LOGLINETYPE'] ); + } + else if ( !isset($content['SourceDiskFile']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_SYSLOGFILE'] ); + } + // Check if file is accessable! + else + { + // Get plain filename for testing! + $content['SourceDiskFileTesting'] = DB_StripSlahes($content['SourceDiskFile']); + + // Take as it is if rootpath! + if ( + ( ($pos = strpos($content['SourceDiskFileTesting'], "/")) !== FALSE && $pos == 0) || + ( ($pos = strpos($content['SourceDiskFileTesting'], "\\\\")) !== FALSE && $pos == 0) || + ( ($pos = strpos($content['SourceDiskFileTesting'], ":\\")) !== FALSE ) || + ( ($pos = strpos($content['SourceDiskFileTesting'], ":/")) !== FALSE ) + ) + { + // Nothing really todo + true; + } + else // prepend basepath! + $content['SourceDiskFileTesting'] = $gl_root_path . $content['SourceDiskFileTesting']; +/* + if ( !is_file($content['SourceDiskFileTesting']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_NOTAVALIDFILE'], $szFileName ); + } +*/ + } + } + // DB Params + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + if ( !isset($content['SourceDBType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DATABASETYPEOPTIONS'] ); + } + else if ( !isset($content['SourceDBName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBNAME'] ); + } + else if ( !isset($content['SourceDBTableType']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLETYPE'] ); + } + else if ( !isset($content['SourceDBServer']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBSERVER'] ); + } + else if ( !isset($content['SourceDBTableName']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBTABLENAME'] ); + } + else if ( !isset($content['SourceDBUser']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_MISSINGPARAM'], $content['LN_CFG_DBUSER'] ); + } + } + else + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_UNKNOWNSOURCE'], $content['SourceDBType'] ); + } + + // --- Verify the Source and report and error if needed! + + // Include LogStream facility + include($gl_root_path . 'classes/logstream.class.php'); + + // First create a tmp source array + $tmpSource['ID'] = $content['SOURCEID']; + $tmpSource['Name'] = $content['Name']; + $tmpSource['SourceType'] = $content['SourceType']; + $tmpSource['MsgParserList'] = $content['MsgParserList']; + $tmpSource['MsgNormalize'] = $content['MsgNormalize']; + $tmpSource['ViewID'] = $content['SourceViewID']; + if ( $tmpSource['SourceType'] == SOURCE_DISK ) + { + $tmpSource['LogLineType'] = $content['SourceLogLineType']; + $tmpSource['DiskFile'] = $content['SourceDiskFileTesting']; // use SourceDiskFileTesting rather then SourceDiskFile as it is corrected + } + // DB Params + else if ( $tmpSource['SourceType'] == SOURCE_DB || $tmpSource['SourceType'] == SOURCE_PDO ) + { + $tmpSource['DBType'] = $content['SourceDBType']; + $tmpSource['DBName'] = $content['SourceDBName']; + $tmpSource['DBTableType'] = $content['SourceDBTableType']; + $tmpSource['DBServer'] = $content['SourceDBServer']; + $tmpSource['DBTableName'] = $content['SourceDBTableName']; + $tmpSource['DBUser'] = $content['SourceDBUser']; + $tmpSource['DBPassword'] = $content['SourceDBPassword']; + $tmpSource['DBEnableRowCounting'] = $content['SourceDBEnableRowCounting']; + $tmpSource['userid'] = $content['userid']; + $tmpSource['groupid'] = $content['groupid']; + } + + // Init the source + InitSource($tmpSource); + + // Create LogStream Object + $stream = $tmpSource['ObjRef']->LogStreamFactory($tmpSource['ObjRef']); + $res = $stream->Verify(); + if ( $res != SUCCESS ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_WITHINSOURCE'], $tmpSource['Name'], GetErrorMessage($res) ); + + if ( isset($extraErrorDescription) ) + $content['ERROR_MSG'] .= "

    " . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); + } + // --- + } + + // --- Now ADD/EDIT do the processing! + if ( !isset($content['ISERROR']) ) + { + // Everything was alright, so we go to the next step! + if ( $_POST['op'] == "addnewsource" ) + { + // Add custom search now! + if ( $content['SourceType'] == SOURCE_DISK ) + { + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, LogLineType, DiskFile, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['SourceType'] . ", + '" . $content['MsgParserList'] . "', + " . $content['MsgNormalize'] . ", + '" . $content['SourceViewID'] . "', + '" . $content['SourceLogLineType'] . "', + '" . $content['SourceDiskFile'] . "', + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + } + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + $sqlquery = "INSERT INTO " . DB_SOURCES . " (Name, SourceType, MsgParserList, MsgNormalize, ViewID, DBTableType, DBType, DBServer, DBName, DBUser, DBPassword, DBTableName, DBEnableRowCounting, userid, groupid) + VALUES ('" . $content['Name'] . "', + " . $content['SourceType'] . ", + '" . $content['MsgParserList'] . "', + " . $content['MsgNormalize'] . ", + '" . $content['SourceViewID'] . "', + '" . $content['SourceDBTableType'] . "', + " . $content['SourceDBType'] . ", + '" . $content['SourceDBServer'] . "', + '" . $content['SourceDBName'] . "', + '" . $content['SourceDBUser'] . "', + '" . $content['SourceDBPassword'] . "', + '" . $content['SourceDBTableName'] . "', + " . $content['SourceDBEnableRowCounting'] . ", + " . $content['userid'] . ", + " . $content['groupid'] . " + )"; + } + + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Do the final redirect + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCE_HASBEENADDED'], $content['Name'] ) , "sources.php" ); + } + else if ( $_POST['op'] == "editsource" ) + { + $result = DB_Query("SELECT ID FROM " . DB_SOURCES . " WHERE ID = " . $content['SOURCEID']); + $myrow = DB_GetSingleRow($result, true); + if ( !isset($myrow['ID']) ) + { + $content['ISERROR'] = true; + $content['ERROR_MSG'] = GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_IDNOTFOUND'], $content['SOURCEID'] ); + } + else + { + // Edit the Search Entry now! + if ( $content['SourceType'] == SOURCE_DISK ) + { + $sqlquery = "UPDATE " . DB_SOURCES . " SET + Name = '" . $content['Name'] . "', + SourceType = " . $content['SourceType'] . ", + MsgParserList = '" . $content['MsgParserList'] . "', + MsgNormalize = " . $content['MsgNormalize'] . ", + ViewID = '" . $content['SourceViewID'] . "', + LogLineType = '" . $content['SourceLogLineType'] . "', + DiskFile = '" . $content['SourceDiskFile'] . "', + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SOURCEID']; + } + else if ( $content['SourceType'] == SOURCE_DB || $content['SourceType'] == SOURCE_PDO ) + { + $sqlquery = "UPDATE " . DB_SOURCES . " SET + Name = '" . $content['Name'] . "', + SourceType = " . $content['SourceType'] . ", + MsgParserList = '" . $content['MsgParserList'] . "', + MsgNormalize = " . $content['MsgNormalize'] . ", + ViewID = '" . $content['SourceViewID'] . "', + DBTableType = '" . $content['SourceDBTableType'] . "', + DBType = " . $content['SourceDBType'] . ", + DBServer = '" . $content['SourceDBServer'] . "', + DBName = '" . $content['SourceDBName'] . "', + DBUser = '" . $content['SourceDBUser'] . "', + DBPassword = '" . $content['SourceDBPassword'] . "', + DBTableName = '" . $content['SourceDBTableName'] . "', + DBEnableRowCounting = " . $content['SourceDBEnableRowCounting'] . ", + userid = " . $content['userid'] . ", + groupid = " . $content['groupid'] . " + WHERE ID = " . $content['SOURCEID']; + } + + $result = DB_Query($sqlquery); + DB_FreeQuery($result); + + // Done redirect! + RedirectResult( GetAndReplaceLangStr( $content['LN_SOURCES_HASBEENEDIT'], $content['Name']) , "sources.php" ); + } + } + } +} + +if ( !isset($_POST['op']) && !isset($_GET['op']) ) +{ + // Default Mode = List Searches + $content['LISTCHARTS'] = "true"; + + // Copy Sources array for further modifications + $content['CHARTS'] = $content['Charts']; + + // --- Process Sources + $i = 0; // Help counter! + foreach ($content['CHARTS'] as &$myChart ) + { + // --- Set Image for Type + // NonNUMERIC are config files Sources, can not be editied + if ( is_numeric($myChart['ID']) ) + { + // Allow EDIT + $myChart['ActionsAllowed'] = true; + + if ( $myChart['userid'] != null ) + { + $myChart['ChartAssignedToImage'] = $content["MENU_ADMINUSERS"]; + $myChart['ChartAssignedToText'] = $content["LN_GEN_USERONLY"]; + } + else if ( $myChart['groupid'] != null ) + { + $myChart['ChartAssignedToImage'] = $content["MENU_ADMINGROUPS"]; + $myChart['ChartAssignedToText'] = GetAndReplaceLangStr( $content["LN_GEN_GROUPONLYNAME"], $myChart['groupname'] ); + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $myChart['ActionsAllowed'] = false; + } + else + { + $myChart['ChartAssignedToImage'] = $content["MENU_GLOBAL"]; + $myChart['ChartAssignedToText'] = $content["LN_GEN_GLOBAL"]; + + // Check if is ADMIN User, deny if normal user! + if ( !isset($_SESSION['SESSION_ISADMIN']) || $_SESSION['SESSION_ISADMIN'] == 0 ) + $myChart['ActionsAllowed'] = false; + } + } + else + { + // Disallow EDIT + $myChart['ActionsAllowed'] = false; + + $myChart['ChartAssignedToImage'] = $content["MENU_INTERNAL"]; + $myChart['ChartAssignedToText'] = $content["LN_GEN_CONFIGFILE"]; + } + // --- + + // --- Set SourceType + if ( $myChart['chart_type'] == CHART_CAKE ) + { + $myChart['ChartTypeImage'] = $content["MENU_CHART_CAKE"]; + $myChart['ChartTypeText'] = $content["LN_CHART_TYPE_CAKE"]; + } + else if ( $myChart['chart_type'] == CHART_BARS_VERTICAL ) + { + $myChart['ChartTypeImage'] = $content["MENU_CHART_BARSVERT"]; + $myChart['ChartTypeText'] = $content["LN_CHART_TYPE_BARS_VERTICAL"]; + } + else if ( $myChart['chart_type'] == CHART_BARS_HORIZONTAL ) + { + $myChart['ChartTypeImage'] = $content["MENU_CHART_BARSHORI"]; + $myChart['ChartTypeText'] = $content["LN_CHART_TYPE_BARS_HORIZONTAL"]; + } + // --- + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $myChart['cssclass'] = "line1"; + else + $myChart['cssclass'] = "line2"; + $i++; + // --- + } + // --- +} +// --- END Custom Code + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +$content['TITLE'] .= " :: " . $content['LN_ADMINMENU_CHARTOPT']; +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "admin/admin_charts.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/images/icons/column-chart-hori.png b/src/images/icons/column-chart-hori.png new file mode 100644 index 0000000000000000000000000000000000000000..3e46cd2b9ed3ff79941920c433295e4c6cb643da GIT binary patch literal 984 zcmV;}11J26P) z000W>0fLJSS^xk8WJyFpRCt_ilYdOq1pvignUJ(ZnM2x{%m$j(F{{H8npr8++*)8G zwdn?n6=G^J9Au7ZY!Mfeb&%{%PVNUP<4`V;^9C{*px0fT2gikShj%CFm!#VMu2|LBj;-rhf-Ki>8LU@?IB{6cwi!H*r@qRLKBX-(&7#hGg((zD&eYNdW$p}07v zQnh;3>Q;|J(d5=>^mlZ+%LBEw7n}eP44nR17icq>Z9uHZCo^$u(uyX=FM3r@A;B zAZKRgXtmyAI6N=YYTNt(kO-i7hkS|o6I&Fw_JNYCU{Wg>a~?z$_^tT>Z`sBnJkq|OM>=x(g<(H4bvxNTdW~}d@=+kf zo7O)Vd=z3NLMoN|2>=iQIQ;U*(zf?xA!f*C`O)bxv*Z!(t|LEFk-O`I%vSsPVV$2n zmNABdywYT0ZQGgY6=H}o%2@@q0BZEKloSkJevyu$R3S1B=yx$#CE z3+!3E=k!q~YrF#hD*@zf$cxeCIO4d={46ti7cs396t{zGE`f24;L|D)iuqB+O(|rA zl*t-L0AK}xeet5#>hqNkSkmC*_c|YcHUwDT9b&VIY_XCS+ca-mJT&O9)9aez-w(-C zr_MV7U^#$YtI~5-TV;WdKBPATc-~L3L*!GB7YTATlyKGc-CdHXti7F)%P1uisbz000McNliru z)&&<0F)-!95s3f*010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00QGl zL_t(|+D%i-Y7{{fJ=O2-nNB8?ID|>iJa8wv5nTwP{)0=G?%cTW7vdj;AP6D|2EmnN zWr!lUP!WxwU`#?ZF_}!9%p}wAdU$JW#e&nXTld^^&#h()3y;^raA-m)ndLd>027=G zE~Vf_AteCLxh&|LP)gAkB}|iLckbM{PO*+PHWtR0E?xA;$Ls$3`ZlJgC&4JP^SuLu z=$bQiWHIn{V+&K$rw~O678V|?(w1w4Wg|&=NDmMK$TE&B<&YVWn!hlY-o)9b=g?~P z2C*ngk)|1vBrz!EB*C-NG-n3~huGb1W53l#o+apu5IvjWZSn%2b~^a%d4!<>b*X;x|b2m0*%TjdQ_z9nou;FstX*`1Ol&$ zjiD8sesv3YjdjY^B*IWanXT?IejWN~Rsr9#kdxCD$0C~)LhV7EVaRvj2nQ3iC$Md8 z-~k~uqKun?Ya#R)YTDa^4|)%LoAOD4XQCHn7!E9ORiHt4*Btn#8r*6lAb|_dO3ol>psL zpjP$a*d`|G!`P-%bUVAcj{JNce>?B84%6~3h>pN2y zST0u8_Wicl`;d1jx362!Obx;n!m09jin71FEW))}rR%DMPTnN^&;WdKBPATc-~L3L*!GB7YTATlyKF*iCiHy|r8F)%QT$=K8Y000McNliru z)&&<0GAc8UyD$I%010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00PKK zL_t(|+KrRlOB7KU#-BN7X3p%FW>QkFrB)kmrWaZEK~bA@Q9)1_Mqm+9LC`*S)ndiHJ_6{6s)a!*xJ^2smxzfNeYoS;MjeS7ynv$$N02&hhTW61`?H3{+cD6H4d zV8~)0I(J~0Q5eoVxOzLl zC-z`p{3Nz!j=*t8A>1577%+u~`c-Q~Bn}cDwy4;(STYTqU`&$HFK}xLf$%ZZVKdSq z2Ezg@i^J{W@c9I!V=f_afbg+~1x0o;oyi*Hk(z@FUj!K=1;!;zQ)MS{frWws9qsMC zHi?T>F_}juUhNIeL4-_5OVFV)1iTKiH{%RC71Qk&m_6HtQ_a^A4u2fTwNkRPPv4wx zTv{R<7Z$;$F;Ui>(@aDqE3jk)QCY8xm7F z!CT^k%B;jQZJqkIazb~`RBfQDZGwpA-sPWy|6OM}Q{Zm}e)R_E%o|q#0000 \ No newline at end of file diff --git a/src/lang/pt_BR/admin.php b/src/lang/pt_BR/admin.php index bc7a549..553273c 100644 --- a/src/lang/pt_BR/admin.php +++ b/src/lang/pt_BR/admin.php @@ -35,6 +35,7 @@ $content['LN_ADMINMENU_VIEWSOPT'] = "Views Options"; $content['LN_ADMINMENU_SEARCHOPT'] = "Search Options"; $content['LN_ADMINMENU_USEROPT'] = "User Options"; $content['LN_ADMINMENU_GROUPOPT'] = "Group Options"; +$content['LN_ADMINMENU_CHARTOPT'] = "Chart Options"; $content['LN_ADMIN_CENTER'] = "Admin center"; $content['LN_ADMIN_UNKNOWNSTATE'] = "Unknown State"; $content['LN_ADMIN_ERROR_NOTALLOWED'] = "You are not allowed to access this page with your user level."; diff --git a/src/templates/admin/admin_charts.html b/src/templates/admin/admin_charts.html new file mode 100644 index 0000000..239b6d8 --- /dev/null +++ b/src/templates/admin/admin_charts.html @@ -0,0 +1,250 @@ + + + + + +

    +
    +
    +
    {LN_GEN_ERRORDETAILS}
    +

    {ERROR_MSG}

    +
    +

    + {LN_GEN_ERRORRETURNPREV} +
    +

    + + + + + + + + + +
    {LN_CHARTS_CENTER}
    +

    + + + + + + + + + + + + + + + + + + + + + + +
    {LN_SOURCES_ID}{LN_SOURCES_NAME}{LN_SOURCES_TYPE}{LN_SOURCES_ASSIGNTO}{LN_GEN_ACTIONS}
    {ID} + + {DisplayName} + + + {DisplayName} + + + {ChartTypeText} {ChartAssignedToText} + +   +   + + +   +   + +
     {LN_CHARTS_ADD}
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {LN_SOURCES_ADDEDIT}
    {LN_SOURCES_NAME}
    {LN_SOURCES_TYPE} + +
    {LN_CFG_VIEW} + +
    {LN_CFG_MSGPARSERS}
    {LN_CFG_NORMALIZEMSG}
    {LN_GEN_USERONLY}
    {LN_GEN_GROUPONLY} + +
    +
    + +
    + + + + + + + + + + +
    {LN_SOURCES_DISKTYPEOPTIONS}
    {LN_CFG_LOGLINETYPE} + +
    {LN_CFG_SYSLOGFILE}
    +
    + +
    + + +
    {LN_CFG_DATABASETYPEOPTIONS}
    + +
    + + + + + +
    {LN_CFG_DBSTORAGEENGINE} + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {LN_CFG_DBTABLETYPE}
    {LN_CFG_DBSERVER}
    {LN_CFG_DBNAME}
    {LN_CFG_DBTABLENAME}
    {LN_CFG_DBUSER}
    {LN_CFG_DBPASSWORD}
    {LN_CFG_DBROWCOUNTING} + Yes No +
    +
    + + + + + +
    + + + +
    +
    + + + + +

    + +
    + + \ No newline at end of file diff --git a/src/templates/admin/admin_menu.html b/src/templates/admin/admin_menu.html index a639dc4..51a9af5 100644 --- a/src/templates/admin/admin_menu.html +++ b/src/templates/admin/admin_menu.html @@ -7,6 +7,7 @@
    {LN_ADMINMENU_SOURCEOPT} {LN_ADMINMENU_VIEWSOPT} {LN_ADMINMENU_SEARCHOPT}{LN_ADMINMENU_CHARTOPT} {LN_ADMINMENU_USEROPT}
    - - - - + + + + + + - +
    {LN_SOURCES_ID}{LN_SOURCES_NAME}{LN_SOURCES_TYPE}{LN_SOURCES_ASSIGNTO}{LN_CHARTS_ID}{LN_CHARTS_NAME}{LN_CHARTS_ENABLEDONLY}{LN_CHART_TYPE}{LN_CHARTS_ASSIGNTO} {LN_GEN_ACTIONS}
    {ID} - - {DisplayName} - - - {DisplayName} - - + + {DisplayName} + + + {DisplayName} + {ChartTypeText} {ChartAssignedToText} @@ -86,49 +58,57 @@
     {LN_CHARTS_ADD} {LN_CHARTS_ADD}
    - -
    + + - + - + - + + + + + - + - - + + - - + + + + + + @@ -150,96 +130,17 @@
    {LN_SOURCES_ADDEDIT}{LN_CHARTS_ADDEDIT}
    {LN_SOURCES_NAME}{LN_CHARTS_NAME}
    {LN_SOURCES_TYPE}{LN_CHARTS_ENABLED}
    {LN_CHART_TYPE} - + - +
    {LN_CFG_VIEW}{LN_CHART_FIELD} - + - +
    {LN_CFG_MSGPARSERS}{LN_CHART_WIDTH}
    {LN_CFG_NORMALIZEMSG}{LN_CHART_MAXRECORDS}
    {LN_CHART_SHOWPERCENT}

    -
    - - - - - - - - - - -
    {LN_SOURCES_DISKTYPEOPTIONS}
    {LN_CFG_LOGLINETYPE} - -
    {LN_CFG_SYSLOGFILE}
    -
    - -
    - - -
    {LN_CFG_DATABASETYPEOPTIONS}
    - -
    - - - - - -
    {LN_CFG_DBSTORAGEENGINE} - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {LN_CFG_DBTABLETYPE}
    {LN_CFG_DBSERVER}
    {LN_CFG_DBNAME}
    {LN_CFG_DBTABLENAME}
    {LN_CFG_DBUSER}
    {LN_CFG_DBPASSWORD}
    {LN_CFG_DBROWCOUNTING} - Yes No -
    -
    -
    - - - + + +
    - - - +

    From 8c01b590b93aa2836dfc6c3891f30aa6c01e653d Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 15:56:56 +0200 Subject: [PATCH 094/142] Fixed minor spelling issue in chart admin --- src/admin/charts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/charts.php b/src/admin/charts.php index 5846a47..91415bf 100644 --- a/src/admin/charts.php +++ b/src/admin/charts.php @@ -188,7 +188,7 @@ if ( isset($_GET['op']) ) if ( (!isset($_GET['verify']) || $_GET['verify'] != "yes") ) { // This will print an additional secure check which the user needs to confirm and exit the script execution. - PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_CHARTS_WARNDELETESEARCH'], $myrow['Name'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); + PrintSecureUserCheck( GetAndReplaceLangStr( $content['LN_CHARTS_WARNDELETESEARCH'], $myrow['DisplayName'] ), $content['LN_DELETEYES'], $content['LN_DELETENO'] ); } // --- @@ -203,7 +203,7 @@ if ( isset($_GET['op']) ) DB_FreeQuery($result); // Do the final redirect - RedirectResult( GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_HASBEENDEL'], $myrow['Name'] ) , "charts.php" ); + RedirectResult( GetAndReplaceLangStr( $content['LN_CHARTS_ERROR_HASBEENDEL'], $myrow['DisplayName'] ) , "charts.php" ); } else { From 9f6d5c344449a0fd73acc1c8efae95f2517bcac8 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 16:00:07 +0200 Subject: [PATCH 095/142] Fixed reading custom searches from database --- src/include/functions_config.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 298605e..6dcc0c7 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -389,9 +389,22 @@ function LoadSearchesFromDatabase() $myrows = DB_GetAllRows($result, true); if ( isset($myrows ) && count($myrows) > 0 ) { - // Overwrite Search Array with Database one - $CFG['Search'] = $myrows; - $content['Search'] = $myrows; + // Overwrite existing Charts array + unset($CFG['Search']); + + // Loop through all data rows + foreach ($myrows as &$mySearch ) + { + // Append to Chart Array + $CFG['Search'][ $mySearch['ID'] ] = $mySearch; + } + + // Copy to content array! + $content['Search'] = $CFG['Search']; + +// // Overwrite Search Array with Database one +// $CFG['Search'] = $myrows; +// $content['Search'] = $myrows; } } @@ -441,8 +454,7 @@ function LoadChartsFromDatabase() $myrows = DB_GetAllRows($result, true); if ( isset($myrows ) && count($myrows) > 0 ) { - - // Overwrite existing Views array + // Overwrite existing Charts array unset($CFG['Charts']); // Loop through all data rows @@ -454,10 +466,6 @@ function LoadChartsFromDatabase() // Copy to content array! $content['Charts'] = $CFG['Charts']; - -// // Overwrite Search Array with Database one -// $CFG['Charts'] = $myrows; -// $content['Charts'] = $myrows; } } From d24da37836706ed2e981b552cfcf9d4ab4941427 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Sep 2008 16:31:12 +0200 Subject: [PATCH 096/142] Added Preview Button into Charts Admin --- src/images/icons/pie-chart_view.png | Bin 0 -> 949 bytes src/include/functions_common.php | 1 + src/lang/en/admin.php | 2 ++ src/templates/admin/admin_charts.html | 2 ++ 4 files changed, 5 insertions(+) create mode 100644 src/images/icons/pie-chart_view.png diff --git a/src/images/icons/pie-chart_view.png b/src/images/icons/pie-chart_view.png new file mode 100644 index 0000000000000000000000000000000000000000..e374fabedd91500207d03e5600ee071bf49624b4 GIT binary patch literal 949 zcmV;m14{gfP)WdKBPATc-~L3L*!GB7YTATlyKF*iCiHy|r8F)%QT$=K8Y000McNliru z)&&<0GAc8UyD$I%010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00Rw4 zL_t(|+I^EvY)nxU$N%r$H}AeT)0t+BNoc22Lp%CG8$qOqulk5!!B|*H5fTd)Y;5gF zEbLgYAyg0{lD6olsL&l2+>`tN<(zZx;qdRp z%@H^Ld(l(ttdJFY$STowHcCA*6%ERZF^kSUSK@m0<3Q*IyQVVD{POU#`_)U>U`lsifUzJw#3gC~5pK>20FojO)hUfgj1LFihZr))8E(BByxs3or4!aB)tZj}oz zLh@h0!MxL0AbTJS6e>RnNp&JWx*NPzL-+fh#v@gmPcm;GlSZWVr;kgLaYB+b$jU~X zDm@AEb_mh>5e&9}#kZGJa82f*Y-J%7T|<%I=j-hHkQ|Ejh8ZA9Y!$~YQ;V=r5;;_@ zwLyqBp{!yP0)c&4u&xrq@-0XX4PrPNLTTwTu<|f4FiJZ+9``W72*1Q77Z^MT!sNCS z#0J9f7y6K9{Z^)Q=t%`LD|Vtk+6SvufZa}E%!q}|>}Rs`TfI(QO{Hf6l+iU!Md%yQ z^+m(T_%tR>$TaX>Q6NbcI2;1n+PZaScA3rOZH5v&SmH*4LA2BiEP{xS3=oOyvw$&` zgIzG-$g)9}1)Mv77x8$kH}jMR3D4W6pWVJ&*;WnIeR3>AvRcUA>||Yf-MReCU(L=X)1p!>W}iDq01v zw-n{dJ1h-WL#Wz->|moXU!RWD4JuEWcbNSA-zjDE^}RPOolPG<#S=>Zuu9a=qqO@? zc&xekSvcu%MPzCj%W8TeS`P;twW%GsE%kLB%p3c27A%?0`Z7=CVahTyWKx*)v#Yax z+MY9khDSAxoo%f_7Q6NL)R|`~W3* \ No newline at end of file diff --git a/src/templates/admin/admin_charts.html b/src/templates/admin/admin_charts.html index 448df4b..fd9bed3 100644 --- a/src/templates/admin/admin_charts.html +++ b/src/templates/admin/admin_charts.html @@ -54,6 +54,8 @@     +   +
     {showpercent_display}
    - +


    From 4ebb09de98619c0954135e5c5f399aa15360bc15 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 12:13:15 +0200 Subject: [PATCH 098/142] Added new Option TreatNotFoundFiltersAsTrue which is used to handle not found fields when filtering Also fixed minor bugs in the logstream classes which occured during filtering. Added a new helper field "SearchField" into the field definitions. --- src/admin/index.php | 4 + src/admin/views.php | 40 +++--- src/chartgenerator.php | 6 +- src/classes/logstream.class.php | 19 +++ src/classes/logstreamdb.class.php | 200 +++++++++++++------------- src/classes/logstreamdisk.class.php | 133 +++++++++++------- src/classes/logstreampdo.class.php | 201 ++++++++++++++------------- src/include/config.sample.php | 1 + src/include/constants_logstream.php | 11 ++ src/include/functions_common.php | 4 + src/index.php | 22 +++ src/lang/de/main.php | 1 + src/lang/en/admin.php | 2 + src/lang/en/main.php | 2 + src/lang/pt_BR/main.php | 1 + src/templates/admin/admin_index.html | 7 + 16 files changed, 393 insertions(+), 261 deletions(-) diff --git a/src/admin/index.php b/src/admin/index.php index d669fc5..1b4b326 100644 --- a/src/admin/index.php +++ b/src/admin/index.php @@ -133,6 +133,7 @@ if ( isset($_POST['op']) ) if ( isset ($_POST['MiscShowPageRenderStats']) ) { $content['MiscShowPageRenderStats'] = 1; } else { $content['MiscShowPageRenderStats'] = 0; } if ( isset ($_POST['MiscEnableGzipCompression']) ) { $content['MiscEnableGzipCompression'] = 1; } else { $content['MiscEnableGzipCompression'] = 0; } if ( isset ($_POST['SuppressDuplicatedMessages']) ) { $content['SuppressDuplicatedMessages'] = 1; } else { $content['SuppressDuplicatedMessages'] = 0; } + if ( isset ($_POST['TreatNotFoundFiltersAsTrue']) ) { $content['TreatNotFoundFiltersAsTrue'] = 1; } else { $content['TreatNotFoundFiltersAsTrue'] = 0; } if ( isset ($_POST['DebugUserLogin']) ) { $content['DebugUserLogin'] = 1; } else { $content['DebugUserLogin'] = 0; } if ( isset ($_POST['MiscDebugToSyslog']) ) { $content['MiscDebugToSyslog'] = 1; } else { $content['MiscDebugToSyslog'] = 0; } @@ -179,6 +180,7 @@ if ( isset($_POST['op']) ) if ( isset ($_POST['User_MiscShowPageRenderStats']) ) { $USERCFG['MiscShowPageRenderStats'] = 1; } else { $USERCFG['MiscShowPageRenderStats'] = 0; } if ( isset ($_POST['User_MiscEnableGzipCompression']) ) { $USERCFG['MiscEnableGzipCompression'] = 1; } else { $USERCFG['MiscEnableGzipCompression'] = 0; } if ( isset ($_POST['User_SuppressDuplicatedMessages']) ) { $USERCFG['SuppressDuplicatedMessages'] = 1; } else { $USERCFG['SuppressDuplicatedMessages'] = 0; } + if ( isset ($_POST['User_TreatNotFoundFiltersAsTrue']) ) { $USERCFG['TreatNotFoundFiltersAsTrue'] = 1; } else { $USERCFG['TreatNotFoundFiltersAsTrue'] = 0; } // Read Text number fields if ( isset ($_POST['User_ViewMessageCharacterLimit']) && is_numeric($_POST['User_ViewMessageCharacterLimit']) ) { $USERCFG['ViewMessageCharacterLimit'] = $_POST['User_ViewMessageCharacterLimit']; } @@ -210,6 +212,7 @@ if (isset($content['MiscShowDebugGridCounter']) && $content['MiscShowDebugGridCo if (isset($content['MiscShowPageRenderStats']) && $content['MiscShowPageRenderStats'] == 1) { $content['MiscShowPageRenderStats_checked'] = "checked"; } else { $content['MiscShowPageRenderStats_checked'] = ""; } if (isset($content['MiscEnableGzipCompression']) && $content['MiscEnableGzipCompression'] == 1) { $content['MiscEnableGzipCompression_checked'] = "checked"; } else { $content['MiscEnableGzipCompression_checked'] = ""; } if (isset($content['SuppressDuplicatedMessages']) && $content['SuppressDuplicatedMessages'] == 1) { $content['SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['SuppressDuplicatedMessages_checked'] = ""; } +if (isset($content['TreatNotFoundFiltersAsTrue']) && $content['TreatNotFoundFiltersAsTrue'] == 1) { $content['TreatNotFoundFiltersAsTrue_checked'] = "checked"; } else { $content['TreatNotFoundFiltersAsTrue_checked'] = ""; } if (isset($content['DebugUserLogin']) && $content['DebugUserLogin'] == 1) { $content['DebugUserLogin_checked'] = "checked"; } else { $content['DebugUserLogin_checked'] = ""; } if (isset($content['MiscDebugToSyslog']) && $content['MiscDebugToSyslog'] == 1) { $content['MiscDebugToSyslog_checked'] = "checked"; } else { $content['MiscDebugToSyslog_checked'] = ""; } @@ -282,6 +285,7 @@ if ( $content['ENABLEUSEROPTIONS'] ) if ( GetConfigSetting('MiscShowPageRenderStats', $content['MiscShowPageRenderStats'], CFGLEVEL_USER) == 1) { $content['User_MiscShowPageRenderStats_checked'] = "checked"; } else { $content['User_MiscShowPageRenderStats_checked'] = ""; } if ( GetConfigSetting('MiscEnableGzipCompression', $content['MiscEnableGzipCompression'], CFGLEVEL_USER) == 1) { $content['User_MiscEnableGzipCompression_checked'] = "checked"; } else { $content['User_MiscEnableGzipCompression_checked'] = ""; } if ( GetConfigSetting('SuppressDuplicatedMessages', $content['SuppressDuplicatedMessages'], CFGLEVEL_USER) == 1) { $content['User_SuppressDuplicatedMessages_checked'] = "checked"; } else { $content['User_SuppressDuplicatedMessages_checked'] = ""; } + if ( GetConfigSetting('TreatNotFoundFiltersAsTrue', $content['TreatNotFoundFiltersAsTrue'], CFGLEVEL_USER) == 1) { $content['User_TreatNotFoundFiltersAsTrue_checked'] = "checked"; } else { $content['User_TreatNotFoundFiltersAsTrue_checked'] = ""; } // --- // --- Set TextFields! diff --git a/src/admin/views.php b/src/admin/views.php index fdff64e..1c51fc4 100644 --- a/src/admin/views.php +++ b/src/admin/views.php @@ -299,28 +299,30 @@ if ( isset($_POST['op']) ) // Check subop's first! if ( isset($_POST['subop']) ) { - // Get NewColID - $szColId = DB_RemoveBadChars($_POST['newcolumn']); - - // Add a new Column into our list! - if ( $_POST['subop'] == $content['LN_VIEWS_ADDCOLUMN'] && isset($_POST['newcolumn']) ) + if ( isset($_POST['newcolumn']) ) { - // Add New entry into columnlist - $content['SUBCOLUMNS'][$szColId]['ColFieldID'] = $szColId; - - // Set Fieldcaption - if ( isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) - $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; - else - $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $szColId; - - // Set CSSClass - $content['SUBCOLUMNS'][$szColId]['colcssclass'] = count($content['SUBCOLUMNS']) % 2 == 0 ? "line1" : "line2"; + // Get NewColID + $szColId = DB_RemoveBadChars($_POST['newcolumn']); - // Remove from fields list as well - if ( isset($content['FIELDS'][$szColId]) ) - unset($content['FIELDS'][$szColId]); + // Add a new Column into our list! + if ( $_POST['subop'] == $content['LN_VIEWS_ADDCOLUMN'] && isset($_POST['newcolumn']) ) + { + // Add New entry into columnlist + $content['SUBCOLUMNS'][$szColId]['ColFieldID'] = $szColId; + // Set Fieldcaption + if ( isset($content[ $fields[$szColId]['FieldCaptionID'] ]) ) + $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $content[ $fields[$szColId]['FieldCaptionID'] ]; + else + $content['SUBCOLUMNS'][$szColId]['ColCaption'] = $szColId; + + // Set CSSClass + $content['SUBCOLUMNS'][$szColId]['colcssclass'] = count($content['SUBCOLUMNS']) % 2 == 0 ? "line1" : "line2"; + + // Remove from fields list as well + if ( isset($content['FIELDS'][$szColId]) ) + unset($content['FIELDS'][$szColId]); + } } } else if ( isset($_POST['subop_delete']) ) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 339e6f8..4f764d2 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -147,7 +147,11 @@ if ( !$content['error_occured'] ) // echo $myKey . "
    "; $YchartData[] = intval($myData); $XchartData[] = strlen($myKey) > 0 ? $myKey : "Unknown"; - $chartImageMapLinks[] = $content['BASEPATH'] . "index.php?filter=" . strtolower ( $fields[$content['chart_field']]['FieldID'] ) . "%3A%3D" . $myKey . "&search=Search"; + if ( isset($fields[$content['chart_field']]['SearchField']) ) + $chartImageMapLinks[] = $content['BASEPATH'] . "index.php?filter=" . $fields[$content['chart_field']]['SearchField'] . "%3A%3D" . $myKey . "&search=Search"; + else + $chartImageMapLinks[] = ""; + $chartImageMapAlts[] = $content[ $fields[$content['chart_field']]['FieldCaptionID'] ] . ": " . $myKey; $chartImageMapTargets[] ="_top"; } diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index bb35432..a27df92 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -373,6 +373,25 @@ abstract class LogStream { } // --- break; + case "processid": + $tmpKeyName = SYSLOG_PROCESSID; + $tmpFilterType = FILTER_TYPE_NUMBER; + // --- Extra numeric Check + if ( isset($tmpValues) ) + { + foreach( $tmpValues as $mykey => $szValue ) + { + if ( is_numeric($szValue) ) + $tmpValues[$mykey] = $szValue; + } + } + else + { + if ( !is_numeric($tmpArray[FILTER_TMP_VALUE]) ) + $tmpArray[FILTER_TMP_VALUE] = ""; + } + // --- + break; /* BEGIN Eventlog based fields */ case "eventid": $tmpKeyName = SYSLOG_EVENT_ID; diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index b16d6dc..495b9f4 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -100,7 +100,9 @@ class LogStreamDB extends LogStream { return ERROR_DB_INVALIDDBMAPPING; // Create SQL Where Clause first! - $this->CreateSQLWhereClause(); + $res = $this->CreateSQLWhereClause(); + if ( $res != SUCCESS ) + return $res; // Success, this means we init the Pagenumber to ONE! $this->_currentPageNumber = 1; @@ -587,105 +589,115 @@ class LogStreamDB extends LogStream { // Process all filters foreach( $this->_filters[$propertyname] as $myfilter ) { - switch( $myfilter[FILTER_TYPE] ) + // Only perform if database mapping is available for this filter! + if ( isset($dbmapping[$szTableType][$propertyname]) ) { - case FILTER_TYPE_STRING: - // --- Check if user wants to include or exclude! - if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE) - $addnod = ""; - else - $addnod = " NOT"; - // --- + switch( $myfilter[FILTER_TYPE] ) + { + case FILTER_TYPE_STRING: + // --- Check if user wants to include or exclude! + if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE) + $addnod = ""; + else + $addnod = " NOT"; + // --- - // --- Either make a LIKE or a equal query! - if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) - { - $szSearchBegin = " = '"; - $szSearchEnd = "' "; - } - else - { - $szSearchBegin = " LIKE '%"; - $szSearchEnd = "%' "; - } - // --- - - // --- If Syslog message, we have AND handling, otherwise OR! - if ( $propertyname == SYSLOG_MESSAGE ) - $addor = " AND "; - else - $addor = " OR "; - // --- - - // Now Create LIKE Filters - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; - else - { - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_STRING; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; - } - break; - case FILTER_TYPE_NUMBER: - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; - else - { - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; - } - break; - case FILTER_TYPE_DATE: - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= " AND "; - else - { - $tmpfilters[$propertyname][FILTER_VALUE] = ""; - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_DATE; - } - - if ( $myfilter[FILTER_DATEMODE] == DATEMODE_LASTX ) - { - // Get current timestamp - $nNowTimeStamp = time(); - - if ( $myfilter[FILTER_VALUE] == DATE_LASTX_HOUR ) - $nNowTimeStamp -= 60 * 60; // One Hour! - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_12HOURS ) - $nNowTimeStamp -= 60 * 60 * 12; // 12 Hours! - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_24HOURS ) - $nNowTimeStamp -= 60 * 60 * 24; // 24 Hours! - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_7DAYS ) - $nNowTimeStamp -= 60 * 60 * 24 * 7; // 7 days - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_31DAYS ) - $nNowTimeStamp -= 60 * 60 * 24 * 31; // 31 days - else + // --- Either make a LIKE or a equal query! + if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) { - // Set filter to unknown and Abort in this case! - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_UNKNOWN; - break; + $szSearchBegin = " = '"; + $szSearchEnd = "' "; + } + else + { + $szSearchBegin = " LIKE '%"; + $szSearchEnd = "%' "; + } + // --- + + // --- If Syslog message, we have AND handling, otherwise OR! + if ( $propertyname == SYSLOG_MESSAGE ) + $addor = " AND "; + else + $addor = " OR "; + // --- + + // Now Create LIKE Filters + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + else + { + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_STRING; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + } + break; + case FILTER_TYPE_NUMBER: + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + else + { + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + } + break; + case FILTER_TYPE_DATE: + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= " AND "; + else + { + $tmpfilters[$propertyname][FILTER_VALUE] = ""; + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_DATE; } - // Append filter - $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $nNowTimeStamp) . "'"; - } - else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_FROM ) - { - // Obtain Event struct for the time! - $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); - $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; - } - else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_TO ) - { - // Obtain Event struct for the time! - $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); - $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " < '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; - } + if ( $myfilter[FILTER_DATEMODE] == DATEMODE_LASTX ) + { + // Get current timestamp + $nNowTimeStamp = time(); - break; - default: - // Nothing to do! - break; + if ( $myfilter[FILTER_VALUE] == DATE_LASTX_HOUR ) + $nNowTimeStamp -= 60 * 60; // One Hour! + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_12HOURS ) + $nNowTimeStamp -= 60 * 60 * 12; // 12 Hours! + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_24HOURS ) + $nNowTimeStamp -= 60 * 60 * 24; // 24 Hours! + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_7DAYS ) + $nNowTimeStamp -= 60 * 60 * 24 * 7; // 7 days + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_31DAYS ) + $nNowTimeStamp -= 60 * 60 * 24 * 31; // 31 days + else + { + // Set filter to unknown and Abort in this case! + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_UNKNOWN; + break; + } + + // Append filter + $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $nNowTimeStamp) . "'"; + } + else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_FROM ) + { + // Obtain Event struct for the time! + $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); + $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; + } + else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_TO ) + { + // Obtain Event struct for the time! + $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); + $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " < '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; + } + + break; + default: + // Nothing to do! + break; + } + } + else + { + // Check how to treat not found db mappings / filters + if ( GetConfigSetting("TreatNotFoundFiltersAsTrue", 0, CFGLEVEL_USER) == 0 ) + return ERROR_DB_DBFIELDNOTFOUND; } } } diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 409fd4f..7850325 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -681,7 +681,8 @@ class LogStreamDisk extends LogStream { // IF result was unsuccessfull, return success - nothing we can do here. if ( $myResults >= ERROR ) return SUCCESS; - + + // Process all filters if ( $this->_filters != null ) { // Evaluation default for now is true @@ -692,9 +693,9 @@ class LogStreamDisk extends LogStream { { // TODO: NOT SURE IF THIS WILL WORK ON NUMBERS AND OTHER TYPES RIGHT NOW if ( - array_key_exists($propertyname, $this->_filters) && - isset($propertyvalue) && - !(is_string($propertyvalue) && strlen($propertyvalue) <= 0 ) /* Negative because it only matters if the propvalure is a string*/ + array_key_exists($propertyname, $this->_filters) && + isset($propertyvalue) /* && + !(is_string($propertyvalue) && strlen($propertyvalue) <= 0) /* Negative because it only matters if the propvalure is a string*/ ) { // Extra var needed for number checks! @@ -707,62 +708,89 @@ class LogStreamDisk extends LogStream { switch( $myfilter[FILTER_TYPE] ) { case FILTER_TYPE_STRING: - // If Syslog message, we have AND handling! - if ( $propertyname == SYSLOG_MESSAGE ) + // Only filter if value is non zero + if ( strlen($propertyvalue) > 0 && strlen($myfilter[FILTER_VALUE]) > 0 ) { - // Include Filter - if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE ) + // If Syslog message, we have AND handling! + if ( $propertyname == SYSLOG_MESSAGE ) { - if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) === false ) - $bEval = false; - } - // Exclude Filter - else if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) - { - if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) !== false ) - $bEval = false; - } - } - // Otherwise we use OR Handling! - else - { - $bIsOrFilter = true; // Set isOrFilter to true - - // Include Filter - if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE ) - { - if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) - { - if ( strtolower($propertyvalue) == strtolower($myfilter[FILTER_VALUE]) ) - $bOrFilter = true; - } - else - { - if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) !== false ) - $bOrFilter = true; - } - } - // Exclude Filter - else if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) - { - if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) - { - if ( strtolower($propertyvalue) != strtolower($myfilter[FILTER_VALUE]) ) - $bOrFilter = true; - } - else + // Include Filter + if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE ) { if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) === false ) - $bOrFilter = true; + $bEval = false; + } + // Exclude Filter + else if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) + { + if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) !== false ) + $bEval = false; } } - break; + // Otherwise we use OR Handling! + else + { + $bIsOrFilter = true; // Set isOrFilter to true + + // Include Filter + if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE ) + { + if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) + { + if ( strtolower($propertyvalue) == strtolower($myfilter[FILTER_VALUE]) ) + $bOrFilter = true; + } + else + { + if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) !== false ) + $bOrFilter = true; + } + } + // Exclude Filter + else if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) + { + if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) + { + if ( strtolower($propertyvalue) != strtolower($myfilter[FILTER_VALUE]) ) + $bOrFilter = true; + } + else + { + if ( stripos($propertyvalue, $myfilter[FILTER_VALUE]) === false ) + $bOrFilter = true; + } + } + break; + } } break; case FILTER_TYPE_NUMBER: - $bIsOrFilter = true; // Set to true in any case! - if ( $myfilter[FILTER_VALUE] == $arrProperitesOut[$propertyname] ) - $bOrFilter = true; + $bIsOrFilter = true; // Default is set to TRUE + if ( is_numeric($arrProperitesOut[$propertyname]) ) + { + if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE ) + { + if ( $myfilter[FILTER_VALUE] == $arrProperitesOut[$propertyname] ) + $bOrFilter = true; + else + $bOrFilter = false; + } + else if ( $myfilter[FILTER_MODE] & FILTER_MODE_EXCLUDE ) + { + if ( $myfilter[FILTER_VALUE] == $arrProperitesOut[$propertyname] ) + $bOrFilter = false; + else + $bOrFilter = true; + } + } + else + { + // If wanted, we treat this filter as a success! + if ( GetConfigSetting("TreatNotFoundFiltersAsTrue", 0, CFGLEVEL_USER) == 1 ) + $bOrFilter = true; + else + $bOrFilter = false; + } break; case FILTER_TYPE_DATE: // Get Log TimeStamp @@ -786,6 +814,7 @@ class LogStreamDisk extends LogStream { else // WTF default? $nLastXTime = 86400; + // If Nowtime + LastX is higher then the log timestamp, the this logline is to old for us. if ( ($nNowTimeStamp - $nLastXTime) > $nLogTimeStamp ) $bEval = false; @@ -793,7 +822,7 @@ class LogStreamDisk extends LogStream { else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_FROM ) { // Get filter timestamp! - $nFromTimeStamp = GetTimeStampFromTimeString($myfilter[FILTER_VALUE]); + $nFromTimeStamp = GetTimeStampFromTimeString($myfilter[FILTER_VALUE]); // If logtime is smaller then FromTime, then the Event is outside of our scope! if ( $nLogTimeStamp < $nFromTimeStamp ) diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 7ffe344..a0379ef 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -103,8 +103,9 @@ class LogStreamPDO extends LogStream { return ERROR_DB_INVALIDDBMAPPING; // Create SQL Where Clause first! - $this->CreateSQLWhereClause(); -//debug echo $this->_SQLwhereClause; + $res = $this->CreateSQLWhereClause(); + if ( $res != SUCCESS ) + return $res; // Only obtain rowcount if enabled and not done before if ( $this->_logStreamConfigObj->DBEnableRowCounting && $this->_totalRecordCount == -1 ) @@ -615,105 +616,115 @@ class LogStreamPDO extends LogStream { // Process all filters foreach( $this->_filters[$propertyname] as $myfilter ) { - switch( $myfilter[FILTER_TYPE] ) + // Only perform if database mapping is available for this filter! + if ( isset($dbmapping[$szTableType][$propertyname]) ) { - case FILTER_TYPE_STRING: - // --- Check if user wants to include or exclude! - if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE) - $addnod = ""; - else - $addnod = " NOT"; - // --- + switch( $myfilter[FILTER_TYPE] ) + { + case FILTER_TYPE_STRING: + // --- Check if user wants to include or exclude! + if ( $myfilter[FILTER_MODE] & FILTER_MODE_INCLUDE) + $addnod = ""; + else + $addnod = " NOT"; + // --- - // --- Either make a LIKE or a equal query! - if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) - { - $szSearchBegin = " = '"; - $szSearchEnd = "' "; - } - else - { - $szSearchBegin = " LIKE '%"; - $szSearchEnd = "%' "; - } - // --- - - // --- If Syslog message, we have AND handling, otherwise OR! - if ( $propertyname == SYSLOG_MESSAGE ) - $addor = " AND "; - else - $addor = " OR "; - // --- - - // Not create LIKE Filters - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; - else - { - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_STRING; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; - } - break; - case FILTER_TYPE_NUMBER: - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; - else - { - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; - $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; - } - break; - case FILTER_TYPE_DATE: - if ( isset($tmpfilters[$propertyname]) ) - $tmpfilters[$propertyname][FILTER_VALUE] .= " AND "; - else - { - $tmpfilters[$propertyname][FILTER_VALUE] = ""; - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_DATE; - } - - if ( $myfilter[FILTER_DATEMODE] == DATEMODE_LASTX ) - { - // Get current timestamp - $nNowTimeStamp = time(); - - if ( $myfilter[FILTER_VALUE] == DATE_LASTX_HOUR ) - $nNowTimeStamp -= 60 * 60; // One Hour! - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_12HOURS ) - $nNowTimeStamp -= 60 * 60 * 12; // 12 Hours! - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_24HOURS ) - $nNowTimeStamp -= 60 * 60 * 24; // 24 Hours! - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_7DAYS ) - $nNowTimeStamp -= 60 * 60 * 24 * 7; // 7 days - else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_31DAYS ) - $nNowTimeStamp -= 60 * 60 * 24 * 31; // 31 days - else + // --- Either make a LIKE or a equal query! + if ( $myfilter[FILTER_MODE] & FILTER_MODE_SEARCHFULL ) { - // Set filter to unknown and Abort in this case! - $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_UNKNOWN; - break; + $szSearchBegin = " = '"; + $szSearchEnd = "' "; + } + else + { + $szSearchBegin = " LIKE '%"; + $szSearchEnd = "%' "; + } + // --- + + // --- If Syslog message, we have AND handling, otherwise OR! + if ( $propertyname == SYSLOG_MESSAGE ) + $addor = " AND "; + else + $addor = " OR "; + // --- + + // Not create LIKE Filters + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= $addor . $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + else + { + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_STRING; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . $addnod . $szSearchBegin . $myfilter[FILTER_VALUE] . $szSearchEnd; + } + break; + case FILTER_TYPE_NUMBER: + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= ", " . $myfilter[FILTER_VALUE]; + else + { + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_NUMBER; + $tmpfilters[$propertyname][FILTER_VALUE] = $dbmapping[$szTableType][$propertyname] . " IN (" . $myfilter[FILTER_VALUE]; + } + break; + case FILTER_TYPE_DATE: + if ( isset($tmpfilters[$propertyname]) ) + $tmpfilters[$propertyname][FILTER_VALUE] .= " AND "; + else + { + $tmpfilters[$propertyname][FILTER_VALUE] = ""; + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_DATE; } - // Append filter - $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $nNowTimeStamp) . "'"; - } - else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_FROM ) - { - // Obtain Event struct for the time! - $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); - $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; - } - else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_TO ) - { - // Obtain Event struct for the time! - $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); - $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " < '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; - } + if ( $myfilter[FILTER_DATEMODE] == DATEMODE_LASTX ) + { + // Get current timestamp + $nNowTimeStamp = time(); - break; - default: - // Nothing to do! - break; + if ( $myfilter[FILTER_VALUE] == DATE_LASTX_HOUR ) + $nNowTimeStamp -= 60 * 60; // One Hour! + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_12HOURS ) + $nNowTimeStamp -= 60 * 60 * 12; // 12 Hours! + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_24HOURS ) + $nNowTimeStamp -= 60 * 60 * 24; // 24 Hours! + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_7DAYS ) + $nNowTimeStamp -= 60 * 60 * 24 * 7; // 7 days + else if ( $myfilter[FILTER_VALUE] == DATE_LASTX_31DAYS ) + $nNowTimeStamp -= 60 * 60 * 24 * 31; // 31 days + else + { + // Set filter to unknown and Abort in this case! + $tmpfilters[$propertyname][FILTER_TYPE] = FILTER_TYPE_UNKNOWN; + break; + } + + // Append filter + $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $nNowTimeStamp) . "'"; + } + else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_FROM ) + { + // Obtain Event struct for the time! + $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); + $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " > '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; + } + else if ( $myfilter[FILTER_DATEMODE] == DATEMODE_RANGE_TO ) + { + // Obtain Event struct for the time! + $myeventtime = GetEventTime($myfilter[FILTER_VALUE]); + $tmpfilters[$propertyname][FILTER_VALUE] .= $dbmapping[$szTableType][$propertyname] . " < '" . date("Y-m-d H:i:s", $myeventtime[EVTIME_TIMESTAMP]) . "'"; + } + + break; + default: + // Nothing to do! + break; + } + } + else + { + // Check how to treat not found db mappings / filters + if ( GetConfigSetting("TreatNotFoundFiltersAsTrue", 0, CFGLEVEL_USER) == 0 ) + return ERROR_DB_DBFIELDNOTFOUND; } } } diff --git a/src/include/config.sample.php b/src/include/config.sample.php index d2191a2..8c1a14b 100644 --- a/src/include/config.sample.php +++ b/src/include/config.sample.php @@ -78,6 +78,7 @@ $CFG['SearchCustomButtonSearch'] = "error"; // Default search string for the $CFG['EnableIPAddressResolve'] = 1; // If enabled, IP Addresses inline messages are automatically resolved and the result is added in brackets {} behind the IP Address $CFG['SuppressDuplicatedMessages'] = 0; // If enabled, duplicated messages will be suppressed in the main display. +$CFG['TreatNotFoundFiltersAsTrue'] = 0; // If you filter / search for messages, and the fields you are filtering for is not found, the filter result is treaten as TRUE! // --- // --- Define which fields you want to see diff --git a/src/include/constants_logstream.php b/src/include/constants_logstream.php index f5023bd..01c8810 100644 --- a/src/include/constants_logstream.php +++ b/src/include/constants_logstream.php @@ -97,12 +97,14 @@ $fields[SYSLOG_HOST]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_HOST]['Sortable'] = true; $fields[SYSLOG_HOST]['DefaultWidth'] = "80"; $fields[SYSLOG_HOST]['FieldAlign'] = "left"; +$fields[SYSLOG_HOST]['SearchField'] = "source"; $fields[SYSLOG_MESSAGETYPE]['FieldID'] = SYSLOG_MESSAGETYPE; $fields[SYSLOG_MESSAGETYPE]['FieldCaptionID'] = 'LN_FIELDS_MESSAGETYPE'; $fields[SYSLOG_MESSAGETYPE]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_MESSAGETYPE]['Sortable'] = true; $fields[SYSLOG_MESSAGETYPE]['DefaultWidth'] = "90"; $fields[SYSLOG_MESSAGETYPE]['FieldAlign'] = "center"; +$fields[SYSLOG_MESSAGETYPE]['SearchField'] = "messagetype"; // Syslog specific $fields[SYSLOG_FACILITY]['FieldID'] = SYSLOG_FACILITY; @@ -111,24 +113,28 @@ $fields[SYSLOG_FACILITY]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_FACILITY]['Sortable'] = true; $fields[SYSLOG_FACILITY]['DefaultWidth'] = "50"; $fields[SYSLOG_FACILITY]['FieldAlign'] = "center"; +$fields[SYSLOG_FACILITY]['SearchField'] = "facility"; $fields[SYSLOG_SEVERITY]['FieldID'] = SYSLOG_SEVERITY; $fields[SYSLOG_SEVERITY]['FieldCaptionID'] = 'LN_FIELDS_SEVERITY'; $fields[SYSLOG_SEVERITY]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_SEVERITY]['Sortable'] = true; $fields[SYSLOG_SEVERITY]['DefaultWidth'] = "50"; $fields[SYSLOG_SEVERITY]['FieldAlign'] = "center"; +$fields[SYSLOG_SEVERITY]['SearchField'] = "severity"; $fields[SYSLOG_SYSLOGTAG]['FieldID'] = SYSLOG_SYSLOGTAG; $fields[SYSLOG_SYSLOGTAG]['FieldCaptionID'] = 'LN_FIELDS_SYSLOGTAG'; $fields[SYSLOG_SYSLOGTAG]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_SYSLOGTAG]['Sortable'] = true; $fields[SYSLOG_SYSLOGTAG]['DefaultWidth'] = "85"; $fields[SYSLOG_SYSLOGTAG]['FieldAlign'] = "left"; +$fields[SYSLOG_SYSLOGTAG]['SearchField'] = "syslogtag"; $fields[SYSLOG_PROCESSID]['FieldID'] = SYSLOG_PROCESSID; $fields[SYSLOG_PROCESSID]['FieldCaptionID'] = 'LN_FIELDS_PROCESSID'; $fields[SYSLOG_PROCESSID]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_PROCESSID]['Sortable'] = true; $fields[SYSLOG_PROCESSID]['DefaultWidth'] = "65"; $fields[SYSLOG_PROCESSID]['FieldAlign'] = "center"; +$fields[SYSLOG_PROCESSID]['SearchField'] = "processid"; // TODO! EventLog specific $fields[SYSLOG_EVENT_ID]['FieldID'] = SYSLOG_EVENT_ID; @@ -137,30 +143,35 @@ $fields[SYSLOG_EVENT_ID]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_EVENT_ID]['Sortable'] = true; $fields[SYSLOG_EVENT_ID]['DefaultWidth'] = "65"; $fields[SYSLOG_EVENT_ID]['FieldAlign'] = "center"; +$fields[SYSLOG_EVENT_ID]['SearchField'] = ""; $fields[SYSLOG_EVENT_LOGTYPE]['FieldID'] = SYSLOG_EVENT_LOGTYPE; $fields[SYSLOG_EVENT_LOGTYPE]['FieldCaptionID'] = 'LN_FIELDS_EVENTLOGTYPE'; $fields[SYSLOG_EVENT_LOGTYPE]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_EVENT_LOGTYPE]['Sortable'] = true; $fields[SYSLOG_EVENT_LOGTYPE]['DefaultWidth'] = "100"; $fields[SYSLOG_EVENT_LOGTYPE]['FieldAlign'] = "left"; +$fields[SYSLOG_EVENT_LOGTYPE]['SearchField'] = ""; $fields[SYSLOG_EVENT_SOURCE]['FieldID'] = SYSLOG_EVENT_SOURCE; $fields[SYSLOG_EVENT_SOURCE]['FieldCaptionID'] = 'LN_FIELDS_EVENTSOURCE'; $fields[SYSLOG_EVENT_SOURCE]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_EVENT_SOURCE]['Sortable'] = true; $fields[SYSLOG_EVENT_SOURCE]['DefaultWidth'] = "100"; $fields[SYSLOG_EVENT_SOURCE]['FieldAlign'] = "left"; +$fields[SYSLOG_EVENT_SOURCE]['SearchField'] = ""; $fields[SYSLOG_EVENT_CATEGORY]['FieldID'] = SYSLOG_EVENT_CATEGORY; $fields[SYSLOG_EVENT_CATEGORY]['FieldCaptionID'] = 'LN_FIELDS_EVENTCATEGORY'; $fields[SYSLOG_EVENT_CATEGORY]['FieldType'] = FILTER_TYPE_NUMBER; $fields[SYSLOG_EVENT_CATEGORY]['Sortable'] = true; $fields[SYSLOG_EVENT_CATEGORY]['DefaultWidth'] = "50"; $fields[SYSLOG_EVENT_CATEGORY]['FieldAlign'] = "center"; +$fields[SYSLOG_EVENT_CATEGORY]['SearchField'] = ""; $fields[SYSLOG_EVENT_USER]['FieldID'] = SYSLOG_EVENT_USER; $fields[SYSLOG_EVENT_USER]['FieldCaptionID'] = 'LN_FIELDS_EVENTUSER'; $fields[SYSLOG_EVENT_USER]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_EVENT_USER]['Sortable'] = true; $fields[SYSLOG_EVENT_USER]['DefaultWidth'] = "85"; $fields[SYSLOG_EVENT_USER]['FieldAlign'] = "left"; +$fields[SYSLOG_EVENT_USER]['SearchField'] = ""; // Message is the last element, this order is important for the Detail page for now! $fields[SYSLOG_MESSAGE]['FieldID'] = SYSLOG_MESSAGE; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 64c970b..f48c585 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1161,6 +1161,7 @@ function SaveGeneralSettingsIntoDB() WriteConfigValue( "MiscShowPageRenderStats", true ); WriteConfigValue( "MiscEnableGzipCompression", true ); WriteConfigValue( "SuppressDuplicatedMessages", true ); + WriteConfigValue( "TreatNotFoundFiltersAsTrue", true ); WriteConfigValue( "ViewMessageCharacterLimit", true ); WriteConfigValue( "ViewEntriesPerPage", true ); @@ -1194,6 +1195,7 @@ function SaveUserGeneralSettingsIntoDB() WriteConfigValue( "MiscShowPageRenderStats", false, $content['SESSION_USERID'] ); WriteConfigValue( "MiscEnableGzipCompression", false, $content['SESSION_USERID'] ); WriteConfigValue( "SuppressDuplicatedMessages", false, $content['SESSION_USERID'] ); + WriteConfigValue( "TreatNotFoundFiltersAsTrue", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewMessageCharacterLimit", false, $content['SESSION_USERID'] ); WriteConfigValue( "ViewEntriesPerPage", false, $content['SESSION_USERID'] ); @@ -1275,6 +1277,8 @@ function GetErrorMessage($errorCode) return $content['LN_ERROR_DB_INVALIDDBDRIVER']; case ERROR_DB_TABLENOTFOUND: return $content['LN_ERROR_DB_TABLENOTFOUND']; + case ERROR_DB_DBFIELDNOTFOUND: + return $content['LN_ERROR_DB_DBFIELDNOTFOUND']; case ERROR_CHARTS_NOTCONFIGURED: return $content['LN_ERROR_CHARTS_NOTCONFIGURED']; diff --git a/src/index.php b/src/index.php index e418cd8..da66577 100644 --- a/src/index.php +++ b/src/index.php @@ -475,6 +475,28 @@ if ( isset($content['Sources'][$currentSourceID]) ) 'IconSource' => $content['MENU_BULLET_BLUE'] ); } + else if ( $mycolkey == SYSLOG_PROCESSID ) + { + // Set OnClick Menu for SYSLOG_EVENT_ID + $content['syslogmessages'][$counter]['values'][$mycolkey]['hasbuttons'] = true; + + // Menu Option to append filter + if ( strlen($content['searchstr']) > 0 ) + { + $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( + 'ButtonUrl' => '?filter=' . urlencode($content['searchstr']) . '+processid%3A' . $logArray[$mycolkey] . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_ADDTOFILTER'], $logArray[$mycolkey]), + 'IconSource' => $content['MENU_BULLET_GREEN'] + ); + } + + // More Menu entries + $content['syslogmessages'][$counter]['values'][$mycolkey]['buttons'][] = array( + 'ButtonUrl' => '?filter=processid%3A' . $logArray[$mycolkey] . '&search=Search' . $content['additional_url_sourceonly'], + 'DisplayName' => GetAndReplaceLangStr($content['LN_VIEW_FILTERFORONLY'], $logArray[$mycolkey]), + 'IconSource' => $content['MENU_BULLET_BLUE'] + ); + } /* Eventlog based fields */ else if ( $mycolkey == SYSLOG_EVENT_ID ) { diff --git a/src/lang/de/main.php b/src/lang/de/main.php index a0a9b52..960f704 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -79,6 +79,7 @@ $content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden. $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; + $content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least one field could not be found."; $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; $content['LN_GEN_EXPORT_XML'] = "XML"; diff --git a/src/lang/en/admin.php b/src/lang/en/admin.php index 1b4aec4..2d57f84 100644 --- a/src/lang/en/admin.php +++ b/src/lang/en/admin.php @@ -78,6 +78,8 @@ $content['LN_GEN_ACCESSDENIED'] = "Access denied to this function"; $content['LN_GEN_DEFVIEWS'] = "Default selected view"; $content['LN_GEN_DEFSOURCE'] = "Default selected source"; $content['LN_GEN_SUPPRESSDUPMSG'] = "Suppress duplicated messages"; +$content['LN_GEN_TREATFILTERSTRUE'] = "Treat filters of not found fields as true"; + $content['LN_GEN_OPTIONNAME'] = "Option name"; $content['LN_GEN_GLOBALVALUE'] = "Global value"; $content['LN_GEN_PERSONALVALUE'] = "Personal (User)value"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index a39cba1..7fd5697 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -79,6 +79,8 @@ $content['LN_ERROR_DB_NOPROPERTIES'] = "No database properties found"; $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; +$content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least one field could not be found."; + $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; $content['LN_GEN_EXPORT_XML'] = "XML"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 360fe2c..68c2b55 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -83,6 +83,7 @@ $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; $content['LN_ERROR_DB_INVALIDDBMAPPING'] = "Invalid datafield mappings"; $content['LN_ERROR_DB_INVALIDDBDRIVER'] = "Invalid database driver selected"; $content['LN_ERROR_DB_TABLENOTFOUND'] = "Could not find the configured table, maybe misspelled or the tablenames are case sensitive"; + $content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least one field could not be found."; $content['LN_GEN_SELECTEXPORT'] = "> Select Exportformat <"; $content['LN_GEN_EXPORT_CVS'] = "CVS (Comma separated)"; $content['LN_GEN_EXPORT_XML'] = "XML"; diff --git a/src/templates/admin/admin_index.html b/src/templates/admin/admin_index.html index ca06bc4..6a1a414 100644 --- a/src/templates/admin/admin_index.html +++ b/src/templates/admin/admin_index.html @@ -177,6 +177,13 @@
    {LN_GEN_TREATFILTERSTRUE}
     {showpercent_display}
    - + +


    From 6f8e062eb0d2e3daf30ea68a64574117fc7f54f2 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 16:59:10 +0200 Subject: [PATCH 102/142] Fixed a few minor issues and bugs in the new chartgenerator --- src/chartgenerator.php | 33 ++++++++++++------------------ src/classes/logstreamdb.class.php | 5 ++++- src/classes/logstreampdo.class.php | 18 ++++++++++------ src/templates/statistics.html | 3 --- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index e3821ce..19c241f 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -44,6 +44,9 @@ include_once($gl_root_path . 'include/functions_filters.php'); // Include LogStream facility include_once($gl_root_path . 'classes/logstream.class.php'); +// Include basic jpgraph lib +require_once ($gl_root_path . "classes/jpgraph/jpgraph.php"); + InitPhpLogCon(); InitSourceConfigs(); InitFrontEndDefaults(); // Only in WebFrontEnd @@ -124,9 +127,6 @@ if ( !$content['error_occured'] ) { if ( isset($content['Sources'][$currentSourceID]) ) { - // Include basic files needed - require_once ($gl_root_path . "classes/jpgraph/jpgraph.php"); - // Obtain and get the Config Object $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; @@ -140,7 +140,7 @@ if ( !$content['error_occured'] ) $chartData = $stream->GetCountSortedByField($content['chart_field'], $content['chart_fieldtype'], $content['maxrecords']); // If data is valid, we have an array! - if ( is_array($chartData) ) + if ( is_array($chartData) && count($chartData) > 0 ) { // Create Y array! foreach( $chartData as $myKey => $myData) @@ -438,21 +438,15 @@ if ( !$content['error_occured'] ) if ( $content['error_occured'] ) { - // QUICK AND DIRTY! + // Use JpGraph to display errors! + $myError = new JpGraphErrObjectImg(); + $myError->SetTitle($content['LN_GEN_ERRORDETAILS']); + $myError->Raise($content['error_details'], true); + exit; + +/* // QUICK AND DIRTY! $myImage = imagecreatetruecolor( $content['chart_width'], $content['chart_width']); - -/* // create basic colours - $red = ImageColorAllocate($myImage, 255, 0, 0); - $green = ImageColorAllocate($myImage, 0, 255, 0); - $gray = ImageColorAllocate($myImage, 128, 128, 128); - $black = ImageColorAllocate($myImage, 0, 0, 0); - $white = ImageColorAllocate($myImage, 255, 255, 255); - - // Fill image with colour, and create a border - imagerectangle( $myImage, 0, 0, $content['chart_width']-1, $content['chart_width']-1, $gray ); - imagefill( $myImage, 1, 1, $white ); -*/ - + $text_color = imagecolorallocate($myImage, 255, 0, 0); imagestring($myImage, 3, 10, 10, $content['LN_GEN_ERRORDETAILS'], $text_color); imagestring($myImage, 3, 10, 25, $content['error_details'], $text_color); @@ -460,8 +454,7 @@ if ( $content['error_occured'] ) header ("Content-type: image/png"); imagepng($myImage); // Outputs the image to the browser imagedestroy($myImage); // Clean Image resource - - exit; +*/ } // --- diff --git a/src/classes/logstreamdb.class.php b/src/classes/logstreamdb.class.php index e326a86..0fdd2dd 100644 --- a/src/classes/logstreamdb.class.php +++ b/src/classes/logstreamdb.class.php @@ -552,7 +552,10 @@ class LogStreamDB extends LogStream { $aResult[ $myRow[$mySelectFieldName] ] = $myRow['TotalCount']; // return finished array - return $aResult; + if ( count($aResult) > 0 ) + return $aResult; + else + return ERROR_NOMORERECORDS; } else { diff --git a/src/classes/logstreampdo.class.php b/src/classes/logstreampdo.class.php index 14eb412..4abb3d6 100644 --- a/src/classes/logstreampdo.class.php +++ b/src/classes/logstreampdo.class.php @@ -538,7 +538,7 @@ class LogStreamPDO extends LogStream { $this->_logStreamConfigObj->DBType == DB_PGSQL ) { // Helper variable for the select statement - $mySelectFieldName = $mySelectFieldName . "Grouped"; + $mySelectFieldName = $mySelectFieldName . "grouped"; $myDBQueryFieldName = "DATE( " . $myDBFieldName . ") AS " . $mySelectFieldName ; } else if($this->_logStreamConfigObj->DBType == DB_MSSQL ) @@ -550,10 +550,10 @@ class LogStreamPDO extends LogStream { // Create SQL String now! $szSql = "SELECT " . $myDBQueryFieldName . ", " . - "count(" . $myDBFieldName . ") as TotalCount " . + "count(" . $myDBFieldName . ") as totalcount " . " FROM " . $this->_logStreamConfigObj->DBTableName . " GROUP BY " . $mySelectFieldName . - " ORDER BY TotalCount DESC"; + " ORDER BY totalcount DESC"; // Append LIMIT in this case! if ( $this->_logStreamConfigObj->DBType == DB_MYSQL || $this->_logStreamConfigObj->DBType == DB_PGSQL ) @@ -574,12 +574,18 @@ class LogStreamPDO extends LogStream { $iCount = 0; while ( ($myRow = $this->_myDBQuery->fetch(PDO::FETCH_ASSOC)) && $iCount < $nRecordLimit) { - $aResult[ $myRow[$mySelectFieldName] ] = $myRow['TotalCount']; - $iCount++; + if ( isset($myRow[$mySelectFieldName]) ) + { + $aResult[ $myRow[$mySelectFieldName] ] = $myRow['totalcount']; + $iCount++; + } } // return finished array - return $aResult; + if ( count($aResult) > 0 ) + return $aResult; + else + return ERROR_NOMORERECORDS; } else { diff --git a/src/templates/statistics.html b/src/templates/statistics.html index ca1230c..c40ff08 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -51,9 +51,6 @@
    -


    From 422b1f01ff5222ba00f71859cab34fff2be51672 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 17:11:43 +0200 Subject: [PATCH 103/142] Fixed minor issue of the filter links within the charts --- src/chartgenerator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 19c241f..c156bbb 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -145,11 +145,14 @@ if ( !$content['error_occured'] ) // Create Y array! foreach( $chartData as $myKey => $myData) { + // Convert into filter format for submenus + $szEncodedKeyStr = str_replace(' ', '+', $myKey); + // echo $myKey . "
    "; $YchartData[] = intval($myData); $XchartData[] = strlen($myKey) > 0 ? $myKey : "Unknown"; if ( isset($fields[$content['chart_field']]['SearchField']) ) - $chartImageMapLinks[] = $content['BASEPATH'] . "index.php?filter=" . $fields[$content['chart_field']]['SearchField'] . "%3A%3D" . $myKey . "&search=Search"; + $chartImageMapLinks[] = $content['BASEPATH'] . "index.php?filter=" . $fields[$content['chart_field']]['SearchField'] . "%3A%3D" . urlencode($szEncodedKeyStr) . "&search=Search"; else $chartImageMapLinks[] = ""; From 1323fcc3e44f5b6d2a73153627f8baa1ac187ef9 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 17:30:32 +0200 Subject: [PATCH 104/142] Fixed minor bug in database upgrade --- src/chartgenerator.php | 2 +- src/include/functions_common.php | 2 +- src/include/functions_config.php | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index c156bbb..19b2aac 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -151,7 +151,7 @@ if ( !$content['error_occured'] ) // echo $myKey . "
    "; $YchartData[] = intval($myData); $XchartData[] = strlen($myKey) > 0 ? $myKey : "Unknown"; - if ( isset($fields[$content['chart_field']]['SearchField']) ) + if ( isset($fields[$content['chart_field']]['SearchField']) && strlen($myKey) > 0 ) $chartImageMapLinks[] = $content['BASEPATH'] . "index.php?filter=" . $fields[$content['chart_field']]['SearchField'] . "%3A%3D" . urlencode($szEncodedKeyStr) . "&search=Search"; else $chartImageMapLinks[] = ""; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 1a789d1..8646f39 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -66,7 +66,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.5.6"; +$content['BUILDNUMBER'] = "2.5.7"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 6dcc0c7..49fcdac 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -416,6 +416,10 @@ function LoadChartsFromDatabase() // Needed to make global global $CFG, $content; + // Abort reading charts if the database version is below 3, because prior v3, there were no charts table + if ( $content['database_installedversion'] < 3 ) + return; + // --- Create SQL Query // Create Where for USERID if ( isset($content['SESSION_LOGGEDIN']) && $content['SESSION_LOGGEDIN'] ) From b48a0096673d4de463b297bd7e55fa449cd8b64c Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 17:42:42 +0200 Subject: [PATCH 105/142] Nothing changed --- src/admin/searches.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/admin/searches.php b/src/admin/searches.php index 69cf644..90fdd54 100644 --- a/src/admin/searches.php +++ b/src/admin/searches.php @@ -65,6 +65,8 @@ if ( isset($_GET['op']) ) //PreInit these values $content['DisplayName'] = ""; $content['SearchQuery'] = ""; + + // General stuff $content['userid'] = null; $content['CHECKED_ISUSERONLY'] = ""; $content['SEARCHID'] = ""; From ec77371ea77260b5fdb02a84a86f24eb738c2362 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Sep 2008 17:45:27 +0200 Subject: [PATCH 106/142] Added changelog entry --- ChangeLog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ChangeLog b/ChangeLog index ece965c..1ae9ad2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,15 @@ --------------------------------------------------------------------------- +Version 2.5.7 (devel), 2008-09-15 +- Added Statistics page for chart generation. The following charts are + possible right now: Pie, bars vertical and bars horicontal. + All charts can be configured within the Admin Center, if the user system + is installed and enabled. Custom charts can be added as well. +- Added filter / search support for processid, event category and the + event user field +- Added database update, as we now have a new table to store + configured charts into. +- Fixed a few minor filtering issues, specially with numeric filters. +--------------------------------------------------------------------------- Version 2.3.11 (beta), 2008-09-08 - Fix another parsing issue in the logline parser. Most of RFC 3164 formatted syslog messages should now be correctly splitted into their From 0329a74f2a015a06d5f366a82b0e3bee34f441af Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2008 11:07:14 +0200 Subject: [PATCH 107/142] Added Bitstream Vera Fonts into the package, so we have no problems drawing fonts on charts anymore --- src/BitstreamVeraFonts/COPYRIGHT.TXT | 124 ++++++++++++++++++ src/BitstreamVeraFonts/README.TXT | 11 ++ src/BitstreamVeraFonts/RELEASENOTES.TXT | 162 ++++++++++++++++++++++++ src/BitstreamVeraFonts/Vera.ttf | Bin 0 -> 65932 bytes src/BitstreamVeraFonts/VeraBI.ttf | Bin 0 -> 63208 bytes src/BitstreamVeraFonts/VeraBd.ttf | Bin 0 -> 58716 bytes src/BitstreamVeraFonts/VeraIt.ttf | Bin 0 -> 63684 bytes src/BitstreamVeraFonts/VeraMoBI.ttf | Bin 0 -> 55032 bytes src/BitstreamVeraFonts/VeraMoBd.ttf | Bin 0 -> 49052 bytes src/BitstreamVeraFonts/VeraMoIt.ttf | Bin 0 -> 54508 bytes src/BitstreamVeraFonts/VeraMono.ttf | Bin 0 -> 49224 bytes src/BitstreamVeraFonts/VeraSe.ttf | Bin 0 -> 60280 bytes src/BitstreamVeraFonts/VeraSeBd.ttf | Bin 0 -> 58736 bytes src/BitstreamVeraFonts/local.conf | 32 +++++ src/chartgenerator.php | 42 +++--- src/classes/jpgraph/jpg-config.inc.php | 2 + 16 files changed, 351 insertions(+), 22 deletions(-) create mode 100644 src/BitstreamVeraFonts/COPYRIGHT.TXT create mode 100644 src/BitstreamVeraFonts/README.TXT create mode 100644 src/BitstreamVeraFonts/RELEASENOTES.TXT create mode 100644 src/BitstreamVeraFonts/Vera.ttf create mode 100644 src/BitstreamVeraFonts/VeraBI.ttf create mode 100644 src/BitstreamVeraFonts/VeraBd.ttf create mode 100644 src/BitstreamVeraFonts/VeraIt.ttf create mode 100644 src/BitstreamVeraFonts/VeraMoBI.ttf create mode 100644 src/BitstreamVeraFonts/VeraMoBd.ttf create mode 100644 src/BitstreamVeraFonts/VeraMoIt.ttf create mode 100644 src/BitstreamVeraFonts/VeraMono.ttf create mode 100644 src/BitstreamVeraFonts/VeraSe.ttf create mode 100644 src/BitstreamVeraFonts/VeraSeBd.ttf create mode 100644 src/BitstreamVeraFonts/local.conf diff --git a/src/BitstreamVeraFonts/COPYRIGHT.TXT b/src/BitstreamVeraFonts/COPYRIGHT.TXT new file mode 100644 index 0000000..e651be1 --- /dev/null +++ b/src/BitstreamVeraFonts/COPYRIGHT.TXT @@ -0,0 +1,124 @@ +Bitstream Vera Fonts Copyright + +The fonts have a generous copyright, allowing derivative works (as +long as "Bitstream" or "Vera" are not in the names), and full +redistribution (so long as they are not *sold* by themselves). They +can be be bundled, redistributed and sold with any software. + +The fonts are distributed under the following copyright: + +Copyright +========= + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream +Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute +the Font Software, including without limitation the rights to use, +copy, merge, publish, distribute, and/or sell copies of the Font +Software, and to permit persons to whom the Font Software is furnished +to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font +Software without prior written authorization from the Gnome Foundation +or Bitstream Inc., respectively. For further information, contact: +fonts at gnome dot org. + +Copyright FAQ +============= + + 1. I don't understand the resale restriction... What gives? + + Bitstream is giving away these fonts, but wishes to ensure its + competitors can't just drop the fonts as is into a font sale system + and sell them as is. It seems fair that if Bitstream can't make money + from the Bitstream Vera fonts, their competitors should not be able to + do so either. You can sell the fonts as part of any software package, + however. + + 2. I want to package these fonts separately for distribution and + sale as part of a larger software package or system. Can I do so? + + Yes. A RPM or Debian package is a "larger software package" to begin + with, and you aren't selling them independently by themselves. + See 1. above. + + 3. Are derivative works allowed? + Yes! + + 4. Can I change or add to the font(s)? + Yes, but you must change the name(s) of the font(s). + + 5. Under what terms are derivative works allowed? + + You must change the name(s) of the fonts. This is to ensure the + quality of the fonts, both to protect Bitstream and Gnome. We want to + ensure that if an application has opened a font specifically of these + names, it gets what it expects (though of course, using fontconfig, + substitutions could still could have occurred during font + opening). You must include the Bitstream copyright. Additional + copyrights can be added, as per copyright law. Happy Font Hacking! + + 6. If I have improvements for Bitstream Vera, is it possible they might get + adopted in future versions? + + Yes. The contract between the Gnome Foundation and Bitstream has + provisions for working with Bitstream to ensure quality additions to + the Bitstream Vera font family. Please contact us if you have such + additions. Note, that in general, we will want such additions for the + entire family, not just a single font, and that you'll have to keep + both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add + glyphs to the font, they must be stylistically in keeping with Vera's + design. Vera cannot become a "ransom note" font. Jim Lyles will be + providing a document describing the design elements used in Vera, as a + guide and aid for people interested in contributing to Vera. + + 7. I want to sell a software package that uses these fonts: Can I do so? + + Sure. Bundle the fonts with your software and sell your software + with the fonts. That is the intent of the copyright. + + 8. If applications have built the names "Bitstream Vera" into them, + can I override this somehow to use fonts of my choosing? + + This depends on exact details of the software. Most open source + systems and software (e.g., Gnome, KDE, etc.) are now converting to + use fontconfig (see www.fontconfig.org) to handle font configuration, + selection and substitution; it has provisions for overriding font + names and subsituting alternatives. An example is provided by the + supplied local.conf file, which chooses the family Bitstream Vera for + "sans", "serif" and "monospace". Other software (e.g., the XFree86 + core server) has other mechanisms for font substitution. + diff --git a/src/BitstreamVeraFonts/README.TXT b/src/BitstreamVeraFonts/README.TXT new file mode 100644 index 0000000..0f71795 --- /dev/null +++ b/src/BitstreamVeraFonts/README.TXT @@ -0,0 +1,11 @@ +Contained herin is the Bitstream Vera font family. + +The Copyright information is found in the COPYRIGHT.TXT file (along +with being incoporated into the fonts themselves). + +The releases notes are found in the file "RELEASENOTES.TXT". + +We hope you enjoy Vera! + + Bitstream, Inc. + The Gnome Project diff --git a/src/BitstreamVeraFonts/RELEASENOTES.TXT b/src/BitstreamVeraFonts/RELEASENOTES.TXT new file mode 100644 index 0000000..270bc0d --- /dev/null +++ b/src/BitstreamVeraFonts/RELEASENOTES.TXT @@ -0,0 +1,162 @@ +Bitstream Vera Fonts - April 16, 2003 +===================================== + +The version number of these fonts is 1.10 to distinguish them from the +beta test fonts. + +Note that the Vera copyright is incorporated in the fonts themselves. +The License field in the fonts contains the copyright license as it +appears below. The TrueType copyright field is not large enough to +contain the full license, so the license is incorporated (as you might +think if you thought about it) into the license field, which +unfortunately can be obscure to find. (In pfaedit, see: Element->Font +Info->TTFNames->License). + +Our apologies for it taking longer to complete the fonts than planned. +Beta testers requested a tighter line spacing (less leading) and Jim +Lyles redesigned Vera's accents to bring its line spacing to more +typical of other fonts. This took additional time and effort. Our +thanks to Jim for this effort above and beyond the call of duty. + +There are four monospace and sans faces (normal, oblique, bold, bold +oblique) and two serif faces (normal and bold). Fontconfig/Xft2 (see +www.fontconfig.org) can artificially oblique the serif faces for you: +this loses hinting and distorts the faces slightly, but is visibly +different than normal and bold, and reasonably pleasing. + +On systems with fontconfig 2.0 or 2.1 installed, making your sans, +serif and monospace fonts default to these fonts is very easy. Just +drop the file local.conf into your /etc/fonts directory. This will +make the Bitstream fonts your default fonts for all applications using +fontconfig (if sans, serif, or monospace names are used, as they often +are as default values in many desktops). The XML in local.conf may +need modification to enable subpixel decimation, if appropriate, +however, the commented out phrase does so for XFree86 4.3, in the case +that the server does not have sufficient information to identify the +use of a flat panel. Fontconfig 2.2 adds Vera to the list of font +families and will, by default use it as the default sans, serif and +monospace fonts. + +During the testing of the final Vera fonts, we learned that screen +fonts in general are only typically hinted to work correctly at +integer pixel sizes. Vera is coded internally for integer sizes only. +We need to investigate further to see if there are commonly used fonts +that are hinted to be rounded but are not rounded to integer sizes due +to oversights in their coding. + +Most fonts work best at 8 pixels and below if anti-aliased only, as +the amount of work required to hint well at smaller and smaller sizes +becomes astronomical. GASP tables are typically used to control +whether hinting is used or not, but Freetype/Xft does not currently +support GASP tables (which are present in Vera). + +To mitigate this problem, both for Vera and other fonts, there will be +(very shortly) a new fontconfig 2.2 release that will, by default not +apply hints if the size is below 8 pixels. if you should have a font +that in fact has been hinted more agressively, you can use fontconfig +to note this exception. We believe this should improve many hinted +fonts in addition to Vera, though implemeting GASP support is likely +the right long term solution. + +Font rendering in Gnome or KDE is the combination of algorithms in +Xft2 and Freetype, along with hinting in the fonts themselves. It is +vital to have sufficient information to disentangle problems that you +may observe. + +Note that having your font rendering system set up correctly is vital +to proper judgement of problems of the fonts: + + * Freetype may or may not be configured to in ways that may + implement execution of possibly patented (in some parts of the world) + TrueType hinting algorithms, particularly at small sizes. Best + results are obtained while using these algorithms. + + * The freetype autohinter (used when the possibly patented + algorithms are not used) continues to improve with each release. If + you are using the autohinter, please ensure you are using an up to + date version of freetype before reporting problems. + + * Please identify what version of freetype you are using in any + bug reports, and how your freetype is configured. + + * Make sure you are not using the freetype version included in + XFree86 4.3, as it has bugs that significantly degrade most fonts, + including Vera. if you build XFree86 4.3 from source yourself, you may + have installed this broken version without intending it (as I + did). Vera was verified with the recently released Freetype 2.1.4. On + many systems, 'ldd" can be used to see which freetype shared library + is actually being used. + + * Xft/X Render does not (yet) implement gamma correction. This + causes significant problems rendering white text on a black background + (causing partial pixels to be insufficiently shaded) if the gamma of + your monitor has not been compensated for, and minor problems with + black text on a while background. The program "xgamma" can be used to + set a gamma correction value in the X server's color pallette. Most + monitors have a gamma near 2. + + * Note that the Vera family uses minimal delta hinting. Your + results on other systems when not used anti-aliased may not be + entirely satisfying. We are primarily interested in reports of + problems on open source systems implementing Xft2/fontconfig/freetype + (which implements antialiasing and hinting adjustements, and + sophisticated subpixel decimation on flatpanels). Also, the + algorithms used by Xft2 adjust the hints to integer widths and the + results are crisper on open source systems than on Windows or + MacIntosh. + + * Your fontconfig may (probably does) predate the release of + fontconfig 2.2, and you may see artifacts not present when the font is + used at very small sizes with hinting enabled. "vc-list -V" can be + used to see what version you have installed. + +We believe and hope that these fonts will resolve the problems +reported during beta test. The largest change is the reduction of +leading (interline spacing), which had annoyed a number of people, and +reduced Vera's utility for some applcations. The Vera monospace font +should also now make '0' and 'O' and '1' and 'l' more clearly +distinguishable. + +The version of these fonts is version 1.10. Fontconfig should be +choosing the new version of the fonts if both the released fonts and +beta test fonts are installed (though please discard them: they have +names of form tt20[1-12]gn.ttf). Note that older versions of +fontconfig sometimes did not rebuild their cache correctly when new +fonts are installed: please upgrade to fontconfig 2.2. "fc-cache -f" +can be used to force rebuilding fontconfig's cache files. + +If you note problems, please send them to fonts at gnome dot org, with +exactly which face and size and unicode point you observe the problem +at. The xfd utility from XFree86 CVS may be useful for this (e.g. "xfd +-fa sans"). A possibly more useful program to examine fonts at a +variety of sizes is the "waterfall" program found in Keith Packard's +CVS. + + $ cvs -d :pserver:anoncvs@keithp.com:/local/src/CVS login + Logging in to :pserver:anoncvs@keithp.com:2401/local/src/CVS + CVS password: + $ cvs -d :pserver:anoncvs@keithp.com:/local/src/CVS co waterfall + $ cd waterfall + $ xmkmf -a + $ make + # make install + # make install.man + +Again, please make sure you are running an up-to-date freetype, and +that you are only examining integer sizes. + +Reporting Problems +================== + +Please send problem reports to fonts at gnome org, with the following +information: + + 1. Version of Freetype, Xft2 and fontconfig + 2. Whether TT hinting is being used, or the autohinter + 3. Application being used + 4. Character/Unicode code point that has problems (if applicable) + 5. Version of which operating system + 6. Please include a screenshot, when possible. + +Please check the fonts list archives before reporting problems to cut +down on duplication. diff --git a/src/BitstreamVeraFonts/Vera.ttf b/src/BitstreamVeraFonts/Vera.ttf new file mode 100644 index 0000000000000000000000000000000000000000..58cd6b5e61eff273e920942e28041f8ddcf1e1b5 GIT binary patch literal 65932 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

    B-* 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

    1$W7r@e0!HPu@g0mkJPQ3hhEb-G4zU50u9!$ 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 W_YF69v_ymE68(IXMraZLEdC#Jb*K;k literal 0 HcmV?d00001 diff --git a/src/BitstreamVeraFonts/VeraBd.ttf b/src/BitstreamVeraFonts/VeraBd.ttf new file mode 100644 index 0000000000000000000000000000000000000000..51d6111d722981a86766cc99c53f1956e5c0a229 GIT binary patch literal 58716 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

    iNMQtk4J2-M9M;wa-c_Ho$tiZ&$vDhb zV_i5TYIIh=6+MD$K^msx*<4Sz1gkpIu6E(S;|mu}wYGP3gMZAue>! zL)AR`&}{fEKh^)EVdEqmyN9hDC{2)zvk#5F@wGducjU{-L!(}q9@Pwwo+#S3{Ek0-Biw6%TPB{RHTA`kctf|8~UY^9D82Mk3lwy@XK&)7_K^HsQ(g;p177 z!)i`WT9FDxB%H@F3!wEhnuC>*K%7i9*uI(-5R6Y2E0MC$HDVgbk#P;A&1KVUZkxyE zwfJn%Uuno{FJoZ)$+7kv-{;#_;a-E9QseY-fB2gR+1`Oy;4~XfS zkt^*0i|9`(!tArAnfJAXyUZnWUa887cv;J+Py>e*YT^O~2AVLdC)M*rsq<%~dZvOl zZ>F|p-4U16KVqTS?J>=fUJ@K?VE+=VK35iL` z_(~p5OV7y6%7&sLuYHGW@;eq3b}A}{1AJ-Mvhs?`ZdKhQJ$hc->$=|8_vzcO|A2vm z2H$YwO*ap@W$3NLhL5;y`o>=ggfqf5Dv#?^?8Y z$YN`<{DO-FN>35B_HLZ`b_KLl3Wgsit<@_Lu8+?0n_bU9Y{qyME6bd*6I(-=E&zf8d>lKR3Sn-of`jICS`k zIC|{(iNE~y!;k*<@ySm<{p@qZcB(>!%S9SySxI8KXu+1aFjvKR-&49-8Vh^%<s>xx@HP(pep&NHN6{y)C(#ttb>;)pp?&)AKZ#B<_a zu}Zulp1?ls_rZE(wRl?mL2N;z{|44J_lQTuzY&9Ejd)T#0MEit#CNc4cu{X zUX5rJ4WbtFl{>`4V!U`yOb~w-?}|g>pm<+=Aifq8#Zhq>d)#jmlf<`TtvG=>$8j-P zG>LDpZ}e0FOKmYjOcyi7CNWFQ5wpcyF;6TI^TnNFq4=k`ODq>In?*HR zXN6cP&WL{@5>B&-#%j_6YpcJAUr^`M;tND)`5beykHtyxnfOqA3h$a7v2?GBU1C43 zcZyfU0sP)A{v_^?J^Q0r7rWXj*5m3afe+?5Fsq{j%m3f&SK<)?r)fSO6OSwJqn0@2 zM*TEA4@uTRylvbmR;z2pCa5uc8qZ@TKTo`E*ebT;+yY6Lhvy#|tYU`YCNT@YM`W_u zSu4ij{0ir@I3L7066Z5Gt7HF%8?KmYC>CGhTuFJvPh|_#*+Sfp?=DtjqSyni!9?R0 z{4Uf_<68W`PVAA-)7i4dSd9BfXF4bL(0wE7p?;$O8uRe%ZSknO0C`p89`ZCF6Wxp* zM2`Ai%QuLxFp}y*IOBIUa5$uZ1~^Bl6U8ufw|Gg}%fBPl8Df|L@p*A?q*^CllK0Zt zvIo9zd~F^oUZneKwf-KZGyDhdzm0kx!^*c#Y*I@_*fd`BQS(F?*NMtj$_r{N>;Xr= zOarPnPO3N6IbJ8ipK&6*t~eP-;72&d&lr9ufMXQbm94lDer->}tIdhJW9JW1hxVz( zxeV7%H6-PzL9r5EYA<1+Opcv72vt7=&kqpZ=2~d2-xvAD7nx^T+Y@;ts;5P*GD1|S zd%-UQ#1m>x%O}byQLNk}p24}mm?KKf;~1|&)c(YKl&3mW48Z$FzJ3&Z)d4)z0lNnZ z<}1NGLi9qLTZW2>=6zxq@PTP7Sqh0`IQM`jkKtq@*OI`uOQ5YOP; zhaJm1;6$B7G4V0ZNAdonwpHMJY7c6Q*e_kj`0osU^Kb?Tht`vLEp}2nAkX`8Zozo~ zCoD9jh4?*j<~l z$+*Wy*A{Fp22x*jNLa!{qO*1ub60FUj3$B6H#CmejR9JH7qqBu;lZ~#pB_sNtF}au z1dUJ%EbmkC-6IV;kqpd*vqUy@Bsn4%HWBS%0d|eZ7ad`(S13Bcvbk7v7A07VmWr;T z3?pBKs1)5qmFO-aqKD`yu0_nV>kwP)dc^(ei*;^)L{b?j28qGq2E^gIN!%=kh+D)^ zaVug~4HqNCZO~}nE=GybVvN9mj`3hT#)65E`ja8!r$Wk4hn$}Y89y6Ro@6|t(6Nla z3-Wz2B>Ymy_~nrAB;D`E$V21OeUQx$V4V03#--n46#5^?-G?!XJc4lvk{6@alaSd@ zL0bO~di19;Hf_W>vKb@Ob7Bjm_4DEdu@&Rli{g*4q^QA2wM}dnFY|anBLIyCuZh>i zZcz^@|AyEr-V|@a>h4b%>-J-Ocn71~pE1V0i;+zq(GKxwMq}6sj5mM9=<-o)r1=En z%x4&PPKm$s81$w12gcN|F@`l^wE70))me;y-w7H|zsKnLgE)sVi$<&q7^i6@Yk^{3 zf;}cIAz^clZ9cKe)bN91 zR|Y9dl>ex=sE?~(8oL`87*CrjvAe@>OxsL{P2Zabn=e>4L-Ew#`jB;>^p|h$Th*W8F5BGcUQZ=@|1fm_h z^v(7i@CW=${crle4rB%H3p^JL21f)p2LF{%nXo*ued2?ON0LS)M^e&L{vPTZdNK4? zYDVh#)H&g);f3LQ!)wFOg#Q?REqow+H2hikoA57b#x!qQT3SBlY1gL>NgJCsGi^!Q z18I+^J)5>IZBJTb+F#O6r+t^6ntprwpELX!TQmNaY0kVat7F!_?DXvZ*{icZXxAsl zm@_ly_c_ONrQF}-{w=Rd-jcj$@;+=|+5X)Q{tmORDY@ov`CaqJ<}b_tpkvRDZ*=^k zAY3rA;J5gDs^Iy89fiKaXA561Jlx69sY|C@I?eC2wbR8SOHrVxtf;Z*n_{i_&f=q; z1D*3b59qwSq^8F7_f;=e?W)>eb)@RksxwtTcUQZ6x`(@0cYmULO{8;VNTj-lACABaderpzuBVr! zkMJj9DR8%4AZiVE84;z?V}NBX(*VoyeO-g^`f$KXzF*I<2`~%N2?4_)p9qE+rZOyT z8H+nzTlN8#wJZRne6mDkOC{j&mR*1&8BXRjue9{Som~uf^Zj~GxgU}v3;mMdF;01$ z;R%LKoaPMDXQPxF!19)d0V^5SGi(BE2iyw)yWngGDbOEq7sJzh=NG^nq#;;|TIYy* zhWlGi;`#u?CfvyruIMg64W;J^Hz<%NLdZW)q%zEE@#DHz%L9OYfKQ&diQz3R%Wyrc zWj)|XPC1TKPT`bO8BSw3o#70IGa1h29Of{b%WxjUMGO}+T*7cE!(|MYb6M34S1?@3 za1-abnbXuT4z&zl<~zGUfjqIBG29;|{0}f};53a4&){i$Q3{&02d$0))-!AZ>TH84CgYO$8ZtD#SE7)T*`16 z!{rRC8LnWslHuBxM&!SlueWh2+d+qp;$^dd)t-k>~{ls3|?2%y2EkZ49?Fd?o6~ z(_PU#z}=j_p5HypPmeG>%J3M&;|xzQY~qw>@K%vG3(%dnpBH}G{M!zSD*2IoHjsDT2-;C#ByLT@bww>JU~<@+N+!(!w~_owjv zsSKwvoX&6t!F|6VAwG3b39Cl%bQj9U0a@!vr3wVHG1E*Gpq8U6xTiu#@e)X>LEQQt;D z>glE2(@RnJvA8ZpJC%xF&>NMC-qG&>`!KwT;Vms;+!@N(!}xjx!;vix+#km|OyPH@ zGMvV6I>Q+ZXEL0{Im~7_hv8g?^B68-xR~J*hD#YPW4N44tY)}^;Yx;^AUR9XFQ^tZ z3~L#_%y2hjxSvZuz_5YuH!}Q!b3Vgq&hzy}hEc$-pv*qNaSUr1)-r5h*a%n#+Wriv zL86v{wsai=&0$~4Fbh0fhCX!?uoq}v2I)a?0N=TZuZMvr%TNlXoW%F1@cpR_r!kz) za0bJf3}-W(!*DLcc?=gZT+DC@!=((DFWH_7Q9ENil&SSWU;bMkM7%pYFjNvARn;F({S+xvb z<~t31-3V9#{D%X!!~F`DkQKnT5!d7S4oSiaF`MtqVK|rJJccA2D+I~L3PG~50<-gW zfFz|WAf@RJ$-@dk@~}c&VAu>;3GS)^q@G&|?xO1uMx{znk6;!!zml=4WUMN|W4&;v z55t=n4g+Uag1aasVOz;^yAm>uQch(!jp1~LGZ@ZfIGf=dhI1LtW4MUnVuni?E@ilk z;c_mqn&AqDD;W}xRbp=&Dt9x(8pf)YAr_2VwFs> zN~TyPQ>;=D#k#Rn?*$3D4{N~P49@`e1s^^PNHV`K_>it&!3wZ1=1yy{GNjfV1l}V^ z?TB>+Lu$uCg4%JASj2EK!zB!tGF--RGsdPtz`PMFOBZmy1?_bbkVb)9P|8rOG^sBP z=Uy-Z*UPZ-bOVmW^;keRYCVo+{y3KT<9HT24(sA}xKFdtaXbqh$FtCJJPRF%5dp2s zv(Ry<^)6h~EOeZpS?D;Pg^mLq=swLt$AS0e0@5sW9M3|>37UnD6Eq7QhjE0`&@6O3 z&ojsKJaatHGsp8hb3E3E?eLW5nUjF|F+iGaP6GaPO{4Q99-Sxg=sbx>=Sko|x=*9? zBtfI|B;d0PkVfZ8f=1^_D18@J+$41;v(%l8^mI*9cQR5EB&j=v+jk1L?-XFW4{LO4 z@u_@gD&LvLcc$^3X?$lo-9( zE$}>v)qEMl3Shn#HKjY_7}hYXWmwO!fng)VCcqkQ>l$wB8gAl$wB8gA@v`!7Tbq(;`g==c-8gAbta=uRcp zT(1HLy8kdzz6wm}n${q%LL);@X|3@pFd^84JG;53@8+If&$7LqWqUo#_Ij4>^(@=# zQDP->p%JH^WqUo#_Ij4>^^jD(aGzv*J#e6uB-`s*w%4<4uV>j_FG#l63zF^if@FKW zAlY6IDMyfGd%YmpUJofpkYszkAlY6oNVeAtlI`_^WP80J*`+Y_Dh8 zz8|ZSvEU?HkL(8@<^fhl*8|owYyv#MX%29j1Dxgnr#ZlB4hTxqz}()z+}^<4-oV`6 zz}()z+}^<4-oV`6z}()z+}^<4-oV`6z}()z+}^<4-oV`60F36M6q2zG%P! z=6-jW``uygcZa#(9pOBWaGpmv&m)}Y5zg}n=Xr$lJi>V%;XIFUo<}&(Bb?_E&hrT8 zd6e@!%6T5;JdbjoM>)@6Fk8++zInSeu2+?P-B`uY^w zcNa9S#LuVDzGDGteSHcoy8w`8m8a0Y1ZfU=iu=(ioB=!uq-fkk;3yz~S^R z&5llS4?4v?=oH$W?$G-B6xy92t*=j^-4|dir`6(VL94~nf>w)P@_6?(Q}}C8avHS2 zq(S;xkOt{%)*yWix=n*bquFT_ORy%EU`;H+nplE0ff|)~N)oJzYu&^WtcfL96HBlr zmS9bw7^Nf$*2J}LVhPs760C_OSQAUICYE4LEWw&sf;F)OYhnr3#1gEDC0G+nuqKvZ zO)SBhSb{aN1Z!dm*2EI5i6vMQORy%EU`;H+n$UuT6-lrrrhF4iuqKvZO)SBhSb{a7 zeJKq|uqLK|6Vtzm>EFZ>tcfMq8J7QNSpJ_wA3q5#I_aIxfx-ky@AM0|$S>R?zi^9O zKwA_*&rTY@3p^9Mz%#K6JQKUXGqGk))68j_IZZRCY34M|oQA&52z;X%gzqxf;bW(a zcOtkJFp_-%|0BzKVVV&Q31M4>0KSuLL+MW218+;pM24y??493<&QOstuieOn3S$z+0ql^Fq#5P!?47-@Sa143@$TE+j& zblX5L&>P5r4kV;>Z3~ILZ^U-0h>L4Ap-8jQ1nUzcwsAxFbQMuGC^I9rnl+iMRux4U z3@~3c$woynnGk{0=s*#AzDR*?44*a+iVC`Yyx6v@(*p&WO-vv>!OLyvVbe?kYys|oHys+vf|ezoh#gEzBb?fR2mtfP z;DpJgA>bSFl-Z8pZx#?0^`We-4u=sL0}~KTMpf`Giq$wf$`_Tz`(V%bVFD@4CNKrH zBe9^0g5Z;fxN`VI4Vdnf8vJ2*>-4ai5&H!6Kno#3>!GU1-egAsAc55iERYTqLFQJA z69DwEq7um2q7uE#3MICHy)B3Zhw>FljD&D4;R6XN9TQr=!}J76EbxW~4>)Wn(h6cw z1wbVyTvDiT;ABU2Y%ZtKj&?w?W;5(IQ7ZV|>;}oX?G;oO@3*0c8Aw~r3N;9-K@~(n zRv<=Yn5c`!DF=Euyk-G1pw$s&8a!(w-ihIfAEu?*fdXtm#Rlq|9Vm~&;aROt+=e_r zYZ1#>(5+BQ#Yu${J%BziP%VTW-i^@%I9*8(ypMLGmUn;$oTw1m4}Ak9fVglWIv^E} z>Nt=yJS|KPMhsm^rw5qCf=HDpfan~fM{92cixNFlpaGnT1&t^O-lTLFe|R~b7W8oX zbb8pWP6yG$j-F3E5j%`V(9-M#1%Q$r^dP#~t#&-N*<1jihn?s_G+_Qwh)T3cD)A3U zqr$ZnFT=f+2!Vqh6W;WzA0#6|606hcG@0NmPX{57H_QlkXoahi1w~r0*)%qxB?S)F!=jd;XvcRLUjQ8ht~a8algYN=>cNiZVl0iLj_ zlo+WUL_-|ELJxYMnht1!2Q-xEfQzshMS>o7H>5cjlz7wydN@2DGa3SoY_i%7__f)< z@7Tx}OpE_<3 zs6g!C>@6A!Ae5LMn!|~Z2Otmv9)KQ3rUx;QiTaaG(dbn$q7CSS9!49eX1C%cCI|5q zrDMLPOB`SVyg)qxQqkdd5oubYAZ8|dfT198jkX{& zEztv9b`?FWc8ihu+Q#&7yWI{T1_FUZCJYy`1N3kw5IxKmr`?V1#_>y|H1dlbCKIx^ zx)H`6g*ctS!sbSKh^UGGc>&EvY?_YV0Cq9iA$)AA8%W!U9x&5Jkwzru)Vf?CzOvg% zNKrCoZR&}rJ$i!MjXvQ)i73Kh0Uv^d4g|SEhvg=4+Z_(4&uc-(s5cmh26wy*dL%f& zfz%g>9yr>_KpV4$5fwnwP?t1$JRT2 zU^h*d%L)`7=+-U`nrMRphl9GL+3UsL>~xp@kV8Br=;2Ma36h7J!|O&K zRt-aS8+w?{$lK;cmC&sr*^#SP)126r7&-fK8v_;uhQn!dI?P0Shso~*)}V)z`V%a> zkr=7rf2UIz9Lqdw;%@b_b#CB>jbQ8@DED&SL?Q#==5Gcjz0zKSlM6{065~l~=1uHro z9_nT$ryFlO-EK|K5($}S+Z00YgCtI%X|=ioDADb9!g~=U06qLpyT$Erl34LMfk!Z4 z^?@(VZnS~{zu;d;oe*%*^To>`NgH}NOcde8gbILFsY_b?em~IzcU^WTDB6Y|{#3** zH(AjO{2mNlHV;i}P=VNCHiL=nXfmRQ+XHrS_&pw%2MprYf@lZ~3?7>eP3dx4&?+V; z_TB;37!F)cFUaIFBQa8AQ;jR=0rYWDIjFP?Y~uo&Hk&7i5>bT9h9bceS^$#R<8*nD z-tW>hPh!yKccK;^FpvUP2mhiLtwlEJpx|P)P4m$EEeGAK$B^}5F?%0?F@Lln%9k--3e$2ogOaDp}DNY zi!L*$1Qcn!@}WgEv*y9uE{_)(&;gd`K5Qng2K(b+dVt}8Cg_piWqN3Kjp^YIx*S$2 z9P|jd+-`4Df-S)G00YtFPp3y3F)hgk5)Q}!;yivh(TJME>~Vo)n#TiXfZ3P_h~X~& zE~iH@-AVL-R0#S&Q$*dzpifBZhsA>I9cVH)#D>=k^045Z)}{TWg?-J^jINid?<;e9hpYX&Zh+v#?gk);cGKtx#+ z5)!-`-Nm0~<>>^c1b-xC5)4_a&i3 zpU>@axV?}&PEVrdwD~l*59t%!Uav1D3E_TFb011oFvUTC29sohY5zBRI1!)+Oz!si zz&44Ai9R5Pq1}Uh>?}Y)KR}PfEEf#kY!1IWF#sNO_(8tQ=z&h{N<@wPZnxhLdT5D$ zzsK)I&fXM|3H0zgs3|>g3Pay?Tar9(py;xDAYaf=EJ%#h*pXbPFj$^tngz^-gZc*E zcVhtZ08NL(pNtayem8LSLGC!cNp6>o3I{zBK@Wc@*^vllwEFFuhbF_|UyMpwUIa)X zSxLeY^u!PzKQu74$7Mm5U@sC;wxpyaKM=!R{6U1rQ-U5z*%}!DLMkK$!DG$ob$Q_;hyLWYBzrwT5#uuCi`(V3 zA~8}UG^d^;q!7zA3-xjjk|HtY;2?Ngkgs zkecF1a$`&jfPoau%$OeTY+_oH4TLaA(uN*xbT_ZdiY!4+5>aqJPew2Sx{E)X6%C>v zpht3!PLH5BIf3XABzh2%`eC!7Q)_54ALtPXqE+0SdH@0a06kK2-2(F=NXe80(9{*gLi8$nq=4cy|8>SClE4T(Ix*a}Gk_U3bip^pJes3@V8PI_?Vwq-Tv5BNeO!R<+0-B&l zY65B$^!i`XyFwFS|kR80B&eyjvMxnNp7Uq~UmPmAT) z;Q>>}=z*PUi5{U)CKAPHiB2=W7C?>hWXw0tMTXjkQrB21nYLhOG1 ZpvJ#@o~VOmV^XC5<&}%wE!WpVGW@^4nRBwBZSQ?Q|7$qO zoOx!R=eIq-eMZI^V`2CuGk5udoZJ^`uVgXy+aowzT`{w?%KN8fyBYfwU)iLFRrPD0 zeEkoH8GEyaF;Ua7w%hwH%ds%VlHwWTt<7s%R{eg(f1YA2^#NSJtEIkc4Kv|~eYpkS z?Jezn%}4(D@*9l#as3kot&OYJwVH}2VgAXCMHaU<)i<`@e|I;ozYo_>YQ+gxo9Q=z z=PrCtZC%y9&bqe!2aNs8F#tSnWmD(sr(>sK{>5izF{uwwm$fi)dn-7l2q{Ft$S#TwH6YdV|Od~=VKj7zbe zY{xxO+{uVS^j`r!#xZB%_+}hSz^^QLELhy&d3o5e!t88e@EVoHvijh$iH&0~29M3m zXvvn83LbNoW{nLVOU!AV9y}Io(%^Y{*s;Q5tmVOL zRF-RP2_BnRoh>|gY-aJJ_5_b@Y+U@S!DELf&3a&F$C|#*wwBgz@92gMZ|=BpdEVuH z-WhG(UEQ5c^{d8uOI9~zd#AUzdn@UxE^lR1S5xQOrpD~SYrNG>o%P-Y^{czQ<;&aK zZtrQTY-(?+?`ran&mKQ+@G`oJF3JjC^xs<9Vg0(L9oE5Fjm6u#y!GDh&icltRrQ@K zy&cUrV&JektQAe2tJ=D{+B#NyaeZr3XA>}O>8xMf-PAbN+uYgIL^n3H)_1lvjrDeS zc`cHxGO<=ypdtJ_w$V4Vh#jIQi%ZSppEtOiZ$8yY%Rt-^}PaA!wjPeYSFO=BCF)3&^)yGe(ERv5fttha4-LwipnLDJjS-P+O94PaKa z1s9?fbar}nnB9a>{7t(nu&>FsKQ6yWMMfEq;qhNTHd zObeP}!651Mg?d{%R(%y#VncIJ=V~knir~h^4sTb-SZ`O)@)b=D-E_vFaeI45FCo;> zvAVI12-a0V46On(^~*cfHt9GRQmlh8Cfj;HWIe_ z_HKwD4lr$fW226}PG?$tO?@Yz?rE>@)X{Hj>S}Aj-U7*?YS*;ls8g5H`UXtWMR$ZC z>hj*Ok|A}C2GQ!w_2AiyYc{?^iht3b3Fe3@y-CE!6ZLRMD^_x}?(qEqv%MHnRqqk>uV-VyaV0GP+ ztQ(kr!r88lcG}%K>xfbHUU07k6LtmnF-96z*0(fyv1>stU0~_L|GsF2SgS7u#RU!B z)ZT2+uDH-!R9;r)T~Jrs@6?yPA^;Rom)~iYpl1hrlPWN!2)l2rMF~WMQKSP&XttSEL}LOq-?f#2JR~> zukx0b%qywFq*dkmt-+~E3UObNcV1!T%wo(ueMU)XN!8-9-lCGKGMcjplTP&MFD>=XD5+XdRarQF9$kw|y|c^8=Mjw- zmd%=8RZ?E&olyu>rq3v)lYk9Wm{~f#WZqcstm*Tn&n9$2i_m33bcV=5H_R?9E3BMe zI@Y_OqHtyj9e|D{m4!0_Ev^LZK?$72Of$>N78K53h!eOjv;-JZT&Uv#1g7KPOdT>E z{W73S^Hh~rf*?Uq7L_b09P6E4S+amQQdC(EaEYI|gK%63dg9WOvLL*~BRWT8Bc%gZ z(cM8R&MKTf3Q!P&s0&k5mgG!?>oi4HNfvwsk|LcNuv$OFrUDr7TR2V2AXSSM>^Ev%JwGcOy>8dwJY=EB_^$MSHr99MYR z3|!OAy71{_O>jzAv9UN?!dBz@Y#dEz?fCJs%E7t1^lweLrwRA0#qUO3`PF&6tXjXK z9@j6x%&T#Smz87PcFc7<>%nLxKJEC_>$7>;c+4~&@O^c*&|IOJvTm5^|391^%=+KP zny&pStiv&H)3GKR5QXbOt0vI69>*(ju7fq>_g7~a&I4M5J{7pL6Zg`LUHIJrihA{L zUhtU4nu0uS(N|iH6^S=ASF=9aG`L~|?jRUia0QKbV>U0&tkz{>jXqmfaD@)c+KsU` zjMAq?ztRi&Fr+MmMYj$GtwoTLOwo)D`gN-ykH+kw8R%+*HY5!VSdrv|ARNt-S<&De z$-30iy^Z>OgnI|BZ^L{BeqM~wnI6!FW+7O+F>cVO8Al{x1kGriCrWvTtw6F#TE^X>q4*} z+M!kYYDBl?xQ;aA5FduP6{0Jx_f?Is8h6s%v?GWLLlg_`mm#VR*8{`vBMfPO8{As1 z!!cam42sYhn*Z12^yz$U#ySQ@*?H`0hk{xMEN95*l+k~G593wZv!5R6>sMtTfk8lr!f zz9)$1v;tu;oXfNuhO|0S+pE)jHRwkAB}8X0t}@OLu7*?^yTH)QL>Kyy6=}keL80N4 z@73k3QKxY7*Vx8n+-dMQMBUIGq}M}!LNYvw;rr6CHKkZ_xUC_%qCHI*81RxL5S>H& zZY@UJhG7?4vx%+KxlLHn)obu$STfrEWO0bXq-}=i8-hD@=Bt*$;0oCx$r*Xd_q6WVnl zyFs3jvG>RqAqeS?;r7_rsSWU#>LE3>x?yV#=^E0op&9=-wAPKgb_grOizYcX>|^r? zxhrOcIOSdZqOhqI;lOKZ|~ zbjM7bS%~j+JR9d`1lKFWU4#Q&KM$X(Af#0|KeXydsAy(mJ%Vf=zExuW;vme^F?WeR zAHh%S7wPks>37k6ML|fXm%prJ4nn}t+d>En%?LU$+T8X59zR?P_M@YtqOEebRpT-&?J}%Vn z4b4jTjg)?pGTKL>>q7U@4lpG4tGdIWQD|+NdkA-uR-&Z=A#vHD#y_8TNZS{}#*wvL z6SQ$%n8ombL-C+t_lJBP!wwDiMTsIIn>l1rLb`f3tc+oOhhH~zmMBZn5ZSMxsE)1; z+2^mu&V;Fvy`bxcd%_`a#PBAKSc&YT0Y7<2NR{G}PR!G* zA9w3et;Y9yKtZ$8XdB9jL-aKA1BAm6ul^YiLb&`h`je;F1qm`@tTvs(iEEy9c- zHR81peGLjU>tp0ggd#1X`m!Sc9`Ykr;@$GcSuAZI(7j6+rz>a%44|WkEwYsJ`p@TVGur?q@T7WD zjo|073Si@}^GobAz^WN11i%-F%kGMe4b;+S(ez3 z`*mE@W_Czw##&zhlDEYyq*|IG9xQ^Ll(qz}e_s>F{Nnjs@!Wp#J*RlK%c(q@E556V zqki#>QylS&@9Y;(UlmVX6;Ep7+XdnYO+20}4nMX)IlNyyHgCG}*aGoruJ~4jIJ93p zVigB9alj)U-Y6dWvDmMPeYj%ZMzL2D-`q1>`Q}EkXSR4?cbxKoCU(b(`!(?mP28u6 zU7FarBT?C@i5-dJ-dwR=6L&?4ZJM}K6I(U0MH8Dfu}Kpf=lYe6jp7bXYzPzU`+lsf z*F@jCrAptAMc)>AU9VqRw^XbP$i05CRuet@MR%jbNRa2d-;kl_uIXu`)`mXe&@wG>SH?(N-W@%WO((jA&_gDlNI9*(sVDMI&x&+%Fn5 zvAo`{EZ0Q6UDVwctJF1$+m^YN+hWBsw^(WwOKx>2OEht-L)7Aq+Wlg2jZ;}XTGTkj zqN}2M;g6MSO)RWhsx17mShz*5THsfzmWrx?yudFiH8H;;TbZwkifmC1Aj%^|nN7?C zRP&~bQv4~^#N0Vy%3Qyg6DCSDQ9L_LDb~d7Fj1t5LQTxl#LO8Rm6@8Du~AIdMBu8p z<*Jx=RZN{ULz$|Hn|>||a8$5BOwj^s#N>@4A73ZQS<0jtVxlG{Xd>@`K zS(+HDiA)S9j1)0CQKY9imGnlD<`jNzRs6Xk)gGgyZWJj>fs&$$WPD5h zvGCz~U!3qJ*%Yr!cu!t`EpSjyvWY~CNDRmcZV`{m`xD+F}Rq%k!;srm+8}I%G&-~y0kNxjX|398R z;e@KlpamQ2bsv-bC5^`d^J~06U+l{od&5_++vGjQDvmk&z{u+rHFBJ?_?QxZO!&>m zWPi#he{b-Utg&+|YP`pOP&g|%Yhm3ioLf+X1@MnfW3|FrXoHeiv$kJuR-Qy3jERj3 z*yZmt_50kc%#vi5bLzcI%)ApO3U;d?2&A=Q_ zV4~g7k_xl1&4DbSr$~yz6s|C)m=%j^F)6C0K&Zv6$SkX7lMpl}smx+^zjyJ!%P0F84?x9au^pwTPeC{zz)!bv&s$1tC3!`72s#|M*g3>_%eMDAf zz5d$du>8r3d7h1@go`{=3P1fXn`@uQz5VR?x0Uk)w`-5^rh$_h;e1*gl|BQ!gmYrR zrSKA&05hq~B&lY1fB&xu?<-R;<&NWFDZo4>jCUNBQjcPu^HL^#FblfI)Ewm%bU`Jt zvcQD+Xj7?Ru?kO&w3LRsY+;V1#Dw^`QL!=6QIX-EFt^L;u-mK_v&rCpq8j0KzxP%& zp*{7zsZ;x>((hBbmvXVH@P+3IpUJ0xQo?-#pM0PG;W#O_w(``r!!5ibT{~9Et5UUR zS`W8qAGSQ+s-5ujIaS)RbY9WSCu<*7ali74R?i>P>JMti4`|Ezqx3nz%MS9#2Hw<; zg9@{>bMkK3LKR7+999>YqB_D;Wv^?lv)p5vmpR5eHz|e9%Nmn3Dn2$YeQf3^2azhv z6CRtAIwsz16)vYRM_JA3<8)zp?X@uQGsw}Yr%s*9?LXzd?0(~N?iZKA%5nZllO|3` zOAlAo$cU(DZc0l_7g14>5wZzr;!A#1R5bohoRFHIpJ$4Sl4^E6(cI;}YyWNSQgU+j zcgm|y{#5(zpKfZrsr#)3$xD8|qUI;h^6*b`8pqatcaOTYrD{pIRO{T*SX3U)Fa0n( zSJDSHQkcsv05x*0uaYGxdb#X zhk2&p9|Y)P?v<$*aq0`49LJM^kS8w}cuL_D(o&KM`MlgoeE9M3Yv7sjPwsu}4?n`E zxNP2 zxgByIj~(lb&r_`NvNOst!F}Ztbcp-%lqul5X9}bO!hY)F7ni^ISI~*R_?BaZBpvLBo<)hqVc!9lyb@O}q{bBprgO)>Kb{DfbtuCwE;0?IBN*(G@dWs)T;Y7e)s-mFG{Pk_O`s+ zzhcFeyFb$Y_R@)@gzU`1U#u&8bbiX*2aE1_B(0D?GCrk1*e76>U6)?e`*Ri0I7tAM~7{9(%`-@bj z&+ZGjn7YKNVt!5m^P)VFSYb1_a(ON-p#4$1r2U1@l@{=b zM|u8%?`f|b(%v8Vq4pcbvtb6P_>w3Nj8h6N|=4Al{>3FX1i5b z)G)z?#9S`fW^p-GkNdr;7ju7&50s8BWbP~PL3^LNgcBrTTv1GZXmKUo@6T7l^1}S$ zb?pFeDbS8}YR3wAi*}%ZS9J0U`Qx9xzWl7Vo%fwx{`$`v&hkEO`&mHt0SwuPiVXdb z5(r}!US|@D zQ93w^xQa9Nx`*##8nn+nf!N_T6JFDDR;+Rv=2L}zJoOr}?b0yAh#fNAAi@l)x*1e4 zsbK~Ku?jq}ND(RfgH+TbywhP76&RRN6w8ur@`Ly>npC$5G(Y3lpP3{Xdx^kVLL(N8S9*Kzou`FD?#_; z4BU@1SSXLDNGFf?fB*Z+`M<4#&TPK^seDYA&=fW+;E8Lrcp6!?Wqzv9EMm;|@Kk6# z-D$db1!|9IGw8F}bay3Hr&20Q4Y@119vB1@sT_$^&NLpKteRBGP)PZcJg}6)dG z_eoD|zy03TxBcNR{~f=rJN4&I>D_?~M~+G}dcME3e*LWJKOdj_i)T9D0md?9W-l<# zWcve|mZU~s)ZECL*hWw0Tvju>QB-^8`&!2Oyh%~AB{`GHk@04`->gQCC4_-8c~aPE zmmpCjQ{+rQp^+mDB**GV0-=yN%{}ITlk6n_*sTj3MgRk@U;%xT2b}e+nKzHA&y=HB zRHg)Efu`=4sHQ|1LRA+=cwvb+OO9G5&o>T;5Pa6^cN*XL!;6n^THDoLdGWzzuQm9W zz1n#CzYgtrF6rdcuim%0?o7LK%{3vA3RH`=jfRfpYVmMt~~ zdsh{z#4WHr?_Jab2;IB5NQ&PxC>9h+P#|HZPiR-qJ!S`y97H6YQm!DmLfXWs)6B`7 zA)2KY*ti7}mC45N3|j%4%%|8U*_-W#D-|i+#=276GJL?ws>9=dIp7))f zd1ltYvUHI@(9AXEe1F~%?J%!oF);?wcZJLZ;fO`-%p9wllfyH0rAko+Z3JT<{7(0@hc-f{z7aB6 zW@xw9t(P|_(j5C7#~kOJF}3zuN3FAV%nJJo#|r0)G3)K?9qXNap7mpP+IKp3I(Lpa zU_anE;5;zq8T&JiXPnQBdCC5g<0a=yW6s#mILLUgpl&1 zZ&3F^=?!gO=)iG0jMVsPz&M;M!y_$SyD*%S#%LA;zbfV zhIpgYnD`)jkUuCrm}pyi3nvxt3D2=(7Suvdbxu4HH`#7(L|qsftN9DT*wc3Q!(smy6*ONO1IFDVWI&XApW` z*YPlfA+tX`O%xRDkQO)V(rsqukaUx3ro7M;IEL-#a?i;r+Q0`IAd;?oq!d6z`K!kR z*`bV;Y8md8Go`>>tP1kX+pT79vM`k#bV^{I0${qFUhF5Yj}llxUp45J&{xE=CK<8p z3yM%>iJb63*AVF}$nlMttUzRhi1>>8Ze6Oo>2VyGyl^h%ldZd7B|lep*TuUUSpNZA zrhF(r4c#y=Fh;6F2A0?1;X3*warj%T4z3m~4QGm2t*$b0iABm0CPWyyB!*Wm1w(Q< zWH#|v2KW{!l27Eyhu6N8%~v&W=5uY2w)X^o_i6qv$^8esPWezgq4PC9;9zx2IP&??5s;S$GSxb!0(PYikh3~2akWlS;gc4q z3PL|cUd>mLpN!c7`qWD!K-bMZ!EfNOI5~VGC*buJ;WvDKLd$tt%Q?Zxr|*Y+MPt7T z*uTSpB99Ut5pJ_PoG!O&4O1c_!a39*(<3i@b#Z%y%-ZJE>a&7$C@t)?hD<1#R%D&dB$)4C(l00P{E2?I# zs;fBNs%e_Mb;{)ZTU%aOUNvLck_`u!ZCkRqXvLJ$>e(|ujb80h(?R7hn*cCs19^5f z(Ut6*aFeS;bovrI{1ZDImd?lyfA;ig$Y;uSU-AT|cqT`yp6K!FsJQ6yqo+fA(>8z_ z3%aiO-hb*6e3606xu@N4K)WFasH(aqW|UjW&l42rFkdoquo5x`zT8Rq(NIEOWDz|1 zFrYYag9D+Y8I+RzyTZp8?fogwcyq~~^tj!-zkg9Xp9) zw6YeZSGm{h(j8Qug7k3;yi@5fzvGeTrVZ4cJ1>rCOQlrh{MAgpS=*{O2V%)`=flT< zC5IOr4ezu)Ff~?T&KRgXC$fUf>7t_oldCp1rj`}CYh$|{NOgy~lnA$zn;fIO3X6!h zCdA83a>n`GSAdZo&4ki`m-KLE2*==l0CLz1KHV}S_Zu0(d_FE95M+o%=z!Ns@-Kh! zgFScLq5VMnc;G*I)e2tt;v3ucyj{by9{=I`dzABszV*PnQSp!7^@(;>Iy3OmkppYn zrDko~eRtF^CcnEH+0WhB^+{|&V3a$|Q5(UVShrl8P#rPf>*it18qcB~UiXz#6lDhO zGX-Gpzd^Q{Y%t6(_N!O7w_cFQa6gWB_#l6}Hz*^N(gXR_dVqF^#xsh)EH zJo0hCV?pjC3wxBI9>pwx$3pN}Lh#64W^)i0WFnYkiL;J?MNjF3jC*ho8qyjn)Bung z-@xtFQiX5e*se1KMDfu9ERQ@I|NuI5DYi~TJ{a$(^ z^;zvdCtmw)h3}T__W|&$d$s@k>(PS<6Ak*4rm(WV24<_Wi3*UiVx}rAq!8$!Rx4W+ zBw);n$;_-kS7KJ;gtfp7*GI4-RB){@Q*a;Qic8iw+wgs%r`-oBfJ6)BT8R83|57bS zkgZywAo&<2rGmgI=1Ee%GSOVD)F>;ZHf5c%&U{dEBf}3zAR5gAF=~t{O-WPzrgU?@ zG+E>;H>*=kC1SQ*q%0OS$`bWfQ>)ahELWGC){AxWI%Si($#l2aF7HtGiBuyc!+jPo zL}&U2;pUG19ny|32A=%}aebH6GmzhZMY=GM5u}y43R)>_N+52ihFw7FN|qF)iv^RE zI912rD5&II91)D@>?fU6bM`uuF8KPFfg9(J7<^EVVQxE52+RjrC5zRr9m&^PRZmlyDZUCgel3KWXUk)OXI9_q?y)rrd`r@({}5_(qqy- z(}ULUNzY4B3O7kk5y_*aMB#R(@eDBPm4&{a-e8X^Q(AW zi=_Yr1vwRou7K7@9R#F=ele8(l`E7wF;Z%VKb9LxHHT;!`a48JPSRcBF{PWlrdy;* zref(Lv`TexMk(x_3UWS*g>nI`fA>1H+GRH828i`Cm~tt; zcBUc_ZmIRK+BjsGQ(f_iUNt^aw<1@le5hv##~mN%2hi|moS}qhD5n6==0>~0k@_2S zX!O)LA9QI#u7h~`X$?Njnbf;sw6jX-<4?6+ zTK9|Je*5wEKdpV|)2k2hA|Ceyue|G#1KLlt&z{v@*na5YpqC@RqwB74c3U6^4i56e zP)x8F&{j}caCb~LY)(r!nk&qT+hWD0aFfSljf=pZ(1Wm$$1|dE5!?%UVE%k0Q9*Y) znGg9}E`k|v(EHq5?`x;D-)cQ)`P=QeNS?8AoklTS2XfqKgH>Ni|A1BLx`|I`=|K~JqS~{btq(`tbhzIMCLxLn zPYM4DBc)GmF)`X7v{QZU8~K!<5T_7MeR1};(IeRz2N|Ev@C`V zs8u658sZ(-dc%;f_=+1%R`SU7p}`JSw`>p$Lb7OLpY zcRqRT)cIE-#ZWS zMLc}vFO?@xPs)FETk*WHi4(uQ_qW=`oN>P>EIP8ic4=DDq;G8gm?xxxB6yD$GQ|`j zcLjVhIeZ*06nTIJs%}-8NruCLLJ#~qJ;@i;yrg!opaU!Da7X8cl4ugCSwC)&Ou{5b zi)h);{n9ix%~a0HO~6vBL#FF__PoiX*POvQU!VME@4z3gv$LEHd=9t#>UqjeqXrFq z`2wJeWWGzXOGz| z2Os?Dh3sQzU$_Z7`IFyh*Q^N1`g?9JPu#ity+4ax{Y$i8YQOp9rzwkai4upwCr}1i z!@B}$I-iie5MUGRqrf<9C}(Kr#NAst(t8aWD zo&RY=p`=-Fl5SG+ ztaFtj>rQE_b&K^e>45T>;zC=IFj-Bu7%AEuYfnc{CH0v1qB$?dc zN!CfW$@aNowldRPY@KbZHdotPq&Cs2w%A(iePW$@7x_){J?6VDcUd2j4v0sTqvDA2 z9rJj23E@OBB3g>zDW^{rOL?Enk+R+%Xw?b_-bLl{npOUSK36m4kFKSF=hyKLJmoOx zXyfw&CBq3e+-oA@AwnbKAxr~-DM|o=3t(;~hz5cKn;E->w6H;GHThYKy7a++=C8Hrv*V_40bP&(ve-v2JJg@LgiJ*e&lc zZ8zU(d5}HCA5spe51SveJZ6289Z|lkK5u#6TB64s7M_QggTf9T`xa;Xol64;s3GFq zz{h`-euTK=cT$`lck~SG62uF(HK-@e?D;_cjr2!ikLZsOY)GgJ$bnT=3E`rN$!0S% zS&(@p+UssB(SLAbem(Vx>fqYaKmj?yqopaL1WChMu~^<9`sDk>0qG$bRFFw z0=9`#wX{OnChe8JseDIzS~+4yGZEwt*us2k} zpaS*sBjT-m_{Vm6U&6Hw;-vT5%?a||{k_WhgV#Ea9+dZhzLn^WTY?@}6S|FjvOo?N zfh`iTa)&B)uq9?_JK4mrf`Zmo*r8MCdwHL7)w;8R;s^>Cq1=xi=EP7xBXRNJkmE&j=1(K?b+Q#D}J~9xroDaG=i->0n9?JW_0meb@B!z zbu)Qvpiyt%RwA4rt<5f39r%lWtyCZWW{iD`MBjtdHFlvm@4)8#Oa35M32j z5#1P77u_ATCVE@cmS`L8B`P9=U|(q(3V+Ia*ml@{*m2l*IQekOQ>lkDEr`@bN`A0- zoSMN=hBAu9L)}s)bc+N#+2qoVCzI#Dvw8Q6cql6zr7}s{v#s_gjo#Kbm%j0i_1%-d z{h(v{{@Fl++ZY6iqUoLPU#u;wYHXmV+c;YW zTFrqZC$YJK_^`R|GDm3yo6F1O(uDP)N(U-D$s;QrBzUM;Ql&%3)VLPuW3v;)!wNrcGJ&9~&Rq zmz{mW=ll4b=eWP8qA~@%1-wcEAP)y`zZ)n`N|d|_iYF{4c2t}k9giakDikHhuyDnL zhbnO3a7uQU66OgjaIt7_sXe+>%(t(1FYzRVBLa7^2#-H3!l@>MwU^$zh<$_rlAQFf za@N>Rdh&AEelqy`&p7K3ZUk!Kq?#sWnz`OVF|;q7T-40@mkmc=U%O_`kDA+`%3Al5 z_U=mqf7sSty?63Wv%u*66AKED)HnX_&8K-*Z~21OdTIIJF7ab~o>(xy>enlG9jctS z;MWve6Ib_us}@Ms?m#N*utv^}C<`l%vEHGU`qszHPq(Jp&?4rHh!iQQ&Xl-h$}%8a zHUemhP_A6MNGwLb!tpi1%!1D98&e5^u^rIb)W_Ry?yz)NJ8T`P9sZ8Ao#8tp4ul_w zaP1D?9kDxdchte~gApouKZANe@eRs2)xUIv@4=pr190)mn;Xf=m5Z=kQ28tVRDFJXjkdyotYb)k?>bWxuy zJSHUXNnFIagkz1jMn&+PIPKk6UV8P|sAI0QSKn464wSbw?C#GI@9r*p0|A=EW@y#& zX?-_mv!4cPl6eZ*psZ|ZEXu7M=^e>~$C;SjA=zN8I7`Bp_yj2~kwyz#i7DJU)?=D$ zww1~$Fi2xd<;`r9$D72=kxo}4Y;%T_MJDIyRZl(BqHmOo7pYdOou^{ca6>e-Uxo}( zFwWKY5A7NF2ZLrMN7sh*aB{FyOAlE?&4st1n_;+&MCXeUiqa#Wel=e^qy4S^>Eh&B z4_80`NveC^TWgQLzINs8pS0Y5B*VL8%i5B=a!MC&?R)+|E3+>6RJPEW9L48Gx=!6s1R<49L2I92Ii4m$cTG!UXOYm zipy!WNfx`}bUDL4QbZU|xFK530@PR;x0FV#cP(*nhYO0-ZAW5W33KuEXqVraIx1*S zhNZ12**79JaHBl|35`4fS#o6DmL9K*8zSN;ort5@s6mrLgAk^{ZHG)&-e8Z2+_UYa zZ5Hc_+xC6n?IWAfTl8=1_I=wTJvp#M`c_VHWYw~tKRvKljTktzY}MgCsg;m2P(#WA zHK;@DcJvUz%`|%R5q}JJXf?1U@+K7)7kdcKj$byVnjtq=s1-{0nbGz#!e!R=s^K~Z zEW@M%{aK(iLY$CPptH%82PAq=W}Qqw`5~V6{_O0v!|1k%)vBd@z~y8f@M(IO;&FM1 z4<4OkLbXWLm_U(|vp|?7r-b5ngUhq3(ofDShsc==eE5a%~b*?&h zU09t5(Ndl;k<~PMs`IIdi)Wnti2KK7eQ#}HVov+5Njwi|%XQc8Q(m1tv-nW$VRiIp zK?(gGaJRBg0>#7QK#@UJyo_KKZ+H<8>2ab(u!^^dEkPO2jKm1iR&a^^un-VVqts63 zaO$OtH>5ofHX4}@gLQ*x9fX|7dQ4NKC~1e!7-`#&jSHT z`dCX|0a@tp;THz}FmO^z8TiPcOHfu9j;z!!F)FowkvgnWJ3=6_y#i2@*j~!e_WDX( zL3X_Nr1mbB{@bOXt}xvK9h}Lo1iGm6%dJGZ6uZOjR2&Ywp**OuAgDcJl~Hk{qN1h9 z7$t6$;m*X3idW*|Mis=k>_SdJCnU!sqiNptahqZtJa$xk46@)#oI^+{Dss8@l!Q1( zdJ0chm5jJisR$JCOda*okRxNn`qcKVKR-Kl>cuPQe}_6A#>(NoOi+Z0p`^2eUvWsf zCJV$k5y+~`T9bur@!yFBZcsJD)lPVx$#8!3-}ER!&bE_V&F041dw!X;;p8?P)!a)* zAD&E^d!}psx7<}9ZFt~YW?dIOnmH}1w(-p41G}Y1{kO#4+C+zP-E+%Zwsfw&ncW!EZB9N1p@Yi3iAE>@(%jMVZ*1lYH&lZ06;fMIlFZb-LZuJ+CK@{1fn&>Z0i%}CvQ&Ol$M<4x1dTF3fa=AF&Lbpa7~Zc+!rtY(~vDF z{yHypb#4vSKX1$(AaaA;A%Y8P_Y-yLYtkP}$NmVBCK%v;HHy>i@RwWGRX$iSW!9Fp ziyoYaDDI6pXEr?h%G!=OKWgcHVszI@o^?_>zV}d7Mfq=5-}g{uS>?a;w))0T&OWW3 zLjcgQeBe119RTq7wOaX2&@dbgqQ?WJ@jM|;4~wMeQ55_9!>A|$AyGo9eZ83P+3enw zLSYe$aE%JaMJak*WQ0XVBl=h9XSjE|wipzDQYIKZb=laP@8s zk0|MMgNTXhIcL@%{b_GU@sC^XIGpNzNjv|NR&Ynpb6;%kU9=B@Fa<_up5WQ*%Bq$( z9Qj+nsNDAmpLh9z1M|x&5f$lPoOnT(vu6SmOp4iJhI2yY0kgv{Ic@lhehQ1ZKt;Mg zOq4p;TQ-?&R`hX)nJi%zbE*fb^(|;rBM89mc?$|)&zbPU!UVH{}3;1vw|gG5LfW$4(V_87u;w3m5#PGnA0Ms$s(208W`dre|Z zk}Xu8oWthu#cZ+uxLq)v5E&62EKJG>1O}f#%aKA)Ysf66Rw!Tlj8RG zE&IVQ6G6Rgh#QwHkHU^T9hjyEg0gBAiYMBGf}bZApBN=N+Ed^WmT=w?9mzIXHrgUW z?{c8R5w+hSa&(QNa!06J@yb*@ay(LIbPp=1XO73{0%MDnV!~Tu`l9+`QR*5NO6t1D za4O;5159}NL2`1$i+V796Gs54Kf*%MBdUI4dAzo#S5@aN{87@eCm&bU-a`oork%W5 zuG>9u82>|wK*7N+cShrR#k$>mn)Wl2ogT=}3Aql~zaA(Z5zsg&#DNL5Q*`qcS7tEW zneX76T*Jd1?s6jF8AXDG@-2}g7y|s$I_Qv6(e?1)GZCQ&eDpTk(8G*3QbiAp@F>KH zGU#AVa?%cjK2}TXZTo&@m{g<{_C9IlFT>wx7+(~rCCVmNEriSE6!Mja90=Y@+r>^uyuFlu+Gpu-X#xnFsx4JZlR* zF?OAFx3onPCdn+IoYW}AujES;l@d`Rx1fiuTk2AJ)a}v^u}kq7dW5K<6JjX8fTub` z54ha;_|^;K%nz6)qYlVZ_P+k46!ZGPwl5J07mJe!pn%_V0RJ1n&(7Bc=BSxeO#+fA zky?e#3T)xGq7pN#j7oS@=@fVmmQFYQKb20uN)^3!`tU?+p)$+5K&i5}NR7%`rQ3R^ zvK_V4z19b;P7Aa*N*cCU5v4?!BW+_vnt8M(1EI?#>1K2TPBKrn=HXGyQc?>ueTQq9_voq zz4k}&+}0!NBc=x}2W>Zp;uG4RgIdm^Z%YSrgtT1tccrJ4$E}C0C#6@Vz9pp0qw=J0ZQOoG>4^zG$~&(h+6a=T7ACC(cjf-@uD$F63)fAAngK#NG6{ zc32$luS5OhEM)R+@XW?{1d8QMG$A73&P3~|jcVc6Otd`FADb22v}A&ps4Sz2J5$K$ ze?#Y>%w;p=m7X*}dK|r{b~K6%mf41NZ+?B1(1;QAXAKbdp{(#86?C=jG7-&fQ>-`H=Af=PTP{=z)!C*J>l|A(tL6)pYPH6+ z$U>#pW~o^;Da~q&r5R<{wXBD)m3y!mdaY}1JJ8IzRk;fdardBIaHs7-wx2&N9+JPQ z>{lPMKE~rlj@3QQ$-etSXzQb{^b3gdA zU)pcoXWM7r@6a1M4HNgZI?Sz8*Uj7^I~R;P@g2gA^FjX+eQZX~SSwu$zMj)~5Ru3}rUokXeFHP<`WH#d21%G}hs{#tLXuQs_hr8c$J@5d7~dY&AQ z66r4`phw*hXbpNlx9|!1LoX+w$KNQCE?-EPcki;KJa1n|$$YaZM^P5mmnF=8`3aLL zOR_G%{pR>-4{Y}4jGCRFz4LBV=*#!_AKnw`X_*@{BkfAk#F$&AJg{nmcb5N;!jhY{ zaPHn!4_(=?J3To8m1$W^;LoWE$ccJhlgUm}YM*<()a zxVTKbWY7H%d6_ja!|RT+WVn*jqZBn;@br!h-oY}!&m~|ayMdD)@Hc(T=pkMjFIhsr z0+K|i9P17|C5rx=tPIy5C&-^PDW6`zH7OrQlmnor4Cq;DlPSWaS9FoMMATwJ{#R8~ zoC#(3E=$bwt?MYMFqv{x8B~v(^W$%u%sSPlB}{u@Q^LeiMH6#&-mM*fK&N^{cynoN zAnmWoxv_yMyW2M;%}NK=$7fZ~*}HoGl}+DBOC~@41AY$Al#XW!EG6LP?l?OOm+Ruf z>+Fflm57=R$p2nH5T&LUv?4?TnB?>{`XTTpPMVaL8y!VI=;u{FZ2Lj|>TjZh-u1|` znrD~Ox4l-y?0jTd^)q7p@$y+W(d!qc&0BE1yl|?1Sjw(nH=gk`l}YI=6}&~y2p52> zhl$|GD&#E_nUDQjpdtw|SWIk;Pl=6*!)KHd8yn+Q677jeiZ97-PfSqkiShKkASuS~ zwFpmKt=F=^lMtKWjmJA2UFxVvUs9ZiVXBKo#wO}dk)jc0(4#gY)2XMB@GzdE98&fn z_lo}FWBh{!pxx;u_j7WzJo%~$ves(Gb$!-w0%m<A4Og(VrM zCj$qJ=gCYfF)}*H0ht}zd} z#t6Hrk5Jdg9<>>>%`!&VH|V`NSV!*%88I^R?~Ys;G2$LJ+x^2vHmmu=Mt(JHqz;|1 zLlFErII>LZp*lWBih{6xL~l?6Y?KE)q{D`~4gNC$>W;w?1E0{y%)dKwVZ;bQ9lGcK z;0V5pOnsgAL+jA%l>h-oq}UN7Glz}5Gh*bzu#wMIS|>Ey{X--2lR9O(FTSw-o*z-h^I`2xIR5()i~HDC zQP8&q*15JaC*GP<<|E$v`5t z2V3f9{_s5Mi{qcK-@-FXwoXgQxnuE>8T%HbO#kM*sf{? zLiC$_@hALQ_Wmc7tT89zeTsIG$2&g$8PDa9Me_ub66ic77JP}rGo^)gB`hk;rbLCA z@ljNCGvZ;3umV#Q_gFh3%yztlL~e|&cH0}Bp2#qFxGO63K2kg*92prF5gz7>hwwmr zbOC}zHF7xg+Rpbc;}v`VupmC6$HjuBIr#Clv=p8FcY|72s0(+8?+{qfc}6W++0z?DA~-KV^8V)U5DesN*9mUHwde|Ps~uKp#FWFYqX&3J3s=Xj&x z40dN=PHtY-1pVcssyDA7R(6ezOT92_XUB31QHSgxr80UDuhLhAp{T+!YV>UKtxbPp>Bu~ky01Rq7+eyrCLD> zxZzT@;zH5-Gg51nS_N|R|31&$8v<(kd;g#J^A60DxifQS&OGNl%Q@#+Zk8^j4!*r? z#;t3T-4m9MyLEnE-gid^Y#x(ZwV~JC+x9uD8po`-;fkE^ng?x^Hg{_%EuME(QPJF< zg;TCn-q@a#GiAugNj_`&xJ3h}?dy>-spR2Tl9IM(=Uy|iZi%I6?7X41yDG9r7u|E1 zbk`#3gtAIa1O0lQRaTpAhOPo^f^=6HwMsEzj%rIqyBnA8+OxEAr+lt)=dPto_h78UDHk*S)+p|{>8?fc>~tj;N*on1C|5+2gde41Opo0(hF8%lHFDN@>b=%jo6Tr#nqt$X}X zh|}moAC|0oXG`Y8yFV}(w*9G7LAS+YA}xQZy0aps@47)ZZO$K2G^O7=yJUG@{pzjp zA3m?nJN)s1dqTalOlzj(4$jJ{Sv6|sr=hs*Ny+oC>N29=6=NFu-Fz&||+nUV51BwdNZk6Q~ zrNu+K6%H#d>{njk$xj|uV8~DI)@iuwBt_eav>pNp)0{gx7q8oV{e9%`uD=%|Py@Nq zVNig9fKIPWlKOxq2mBdM?+8z-2Rum6%t0_zjy2-VaO=*05MG2laPwdW?$5};AWs#m z{#sL0TU%4}*HyQkuDSZ^n#nb%Z`~vrXV1R=`q{H1KW^F-`B6WXjLRAu=Zv2){i)qE zYvcNLy?y7(UPFp2AJV4R)YN=_%gWO=HP>8IbM;?Wtv-EqP0frsD1C18rpWo(v*+Mg z(l%|D3^Q(?aGNTS-y>-US7n@(uk9lJgCop|IX2%n^>zSAnlq;_n0sq+dV^2c;yr z4n7C$v?DR-XSS~k{1P61(XbJWP8Gj^l}$f`!N)6asl4y;-#!#xTG{XRf$yI`|NfA* z+H1drk{LgTz<;ewOWpJEBfC@5mnS8jee=Y(iI5*sj&hG-AUKx|Q8N035u?oLvZx-K zR$gO={^Un=mhOtVyVc&q+1=a2?Z;a31q`leJsI5bYB|`zs z@KbNd=%EhXy?w{LmrIV%pLcvd;ytv13$-4UK8u`?@*|0IWlbbe`m6@LNmkXb)h=w= zzWN!sPw6pKi39)d?Am#0h#5c!2i?o<+Idxu?cGZ*-ZInDp z8D$t{M72?IV@4L)O>KQG@3^%4c+PQy>)5el#Q#4KIf#7q5!T52gwyQ^6fEDVBHESh zPKTw|gFwAL4^$o#g&KjCDBN>TA}AH>eD9RMT8J1@t_b2t;rkGwN(vegp^A1Xrv!0) zOVU0d_lIS>(XNAEmK!gei$s>~j=U@Ff4OyyvQ6qW{awVciX55#_Kt60@vt3A`dRcl z1$Ai-SHYtYQJ6KwVZ@GwBdk|x99`V49P_N&*Fd$f`Z-zcsv(eNugz6T!K4zwLKEHMN@i6K!Uso~0! zp6=Z241AwZ^LoT1$wYBsPKJ9@p)x6>x^Q&QoX)w$J(Kb}2ji3Y9&>i*V2>noZs(F< zV&_zIVv@Zl>=w?RX}8`Z3CzNnMKXFP8-ci9B)x;=aC9;=oM;dMfynYbx|bA`8qC>R zkN9kVj~rVb<`c<#6#e<Ci(XoO&1@I0d28(PNhfl1Zy>_i>f|K#?egB+w$1)}ewXo`5= zrijcq)$hzn^Cvjd3T*i0F?8}IQ}jlfveHcncs#)^z|B&C!I3UqX|T{ib_8kWE-E^L zG;J3hH7fUXyKU^W6*o*Av-(iC)#Iiw$I)#Y20mP~_Q2z}UGw1J{#&Np_QK=0)!ZlN zPnfi3=Cs?^luVd#>vhv^TN_xj{FBG_ynpi@cdR<~_?|y6#%SULjFgfT8dwa%KteM-(85rR|`*Z1miQRJ}ZT^D=Z{3M<0(Wkv|`hk7}cR zcp% zLPUP;q+7Fx;%9|qV9E&XFsBq9+t43f0!2?uOot&aoXDg4A*@x}kD-$j!86QJ5~MEA zuT>3;X5YI%b@_9Tt4b)rl98I&c~o}gu$~Xiyn0>W`bf3G(mKfBJ^t$YC!UbkU--2* z(VCKEvELgAjvYE`MD@Vd_o$y=+xCU>lGX()26>`591=Nc<^)5I-{4G3aQc7M$(E^z zWKU*XCn#QUV&vhP>oXj>UU$7tLpgK`eUuLd72`-S@zvwTQvMDj&4ugn**)@Er*p{Q zk`0-e1skUA`*Y-zcQ+mXXxp?~tm8+{USBWiNHOrvA=Xq5 zy@Q&flBEUVuH%$Z%4oG(8>Q7LwaN@QEKbvEwS~%DWiD3F=V~`v+|^bJzCTx+Yapk3 z3cLx44*E1pC2GrszVg=Ap~^qbeJsD*Iz)TvrPiWW^~L*?H2E~usR?zOhdObN9D{tH zdat<0q?AZniGU`ko0eWUgSka7=Q?m_G^y`?(`xyL_RV>VdD(m$nhl!54QNY_>i2Cl#rZ|!a*)FE}8>j=&Ltk3Fpuup_74i!ZdZw{+Ui^ zeu=wlR&sJl{0&R|>7ybC{`kzxdBx@Ct%-O4Mp>e4jD&_x4wQA%wDl&_^!lzF9+hsq z(4cG#4|r@D;U_<`UO8uQh%AvE_N6{3;_VN5<2M^uWLrvuWl5!Kc5GVo!kLqwxjz03 zgDY=wiAT=?Lw$<|mFWt2tQ_lTG`x~Zu5-we?clLd_2H6EJ#7ZFYnB7E0>3{ra$5i1 zX$4M;1rZIOy1rCaq!-5aEw0GUa1L(0@1;4Bv`F2ReS20UC3>zTsXaz|SJ{ZUGYc%$ z^Tqsd*lZA?bZgLI5KY0ZO`S8gq(7WuGc3zR)rJ8RV!Z|;}ntLq1D91=HjQ~&!P z)GnNqpANQ;Qb!HDve&>hjY++cRjqI3()v3eQN4cSz`p6}%M%lCx-p@o^)aOG({Zpj zI&+;Yx`z{zni9-So`;ksTXhQhi$BqsWDvcahF<;@{quAsl14Z9tTQKRehjS?HUxt1GU%;X-XA`p*bR!8f!o;F%c?r<=Eisy$A)q;Ey+$<%wKst5F+;V|gHF8=cvAP5V)f-I0UM^Md37k*@R!Ot|zXCRQO9+ydbqxFfWUy1zi z{`--CAKBabgjBI*BeJq|lk%mNcRjjy@7^g=)v<4+^vFm5I2PGAW$)f~Qm5yzrhZ3c zapd?PBk!*#Tw)E(*aVAHv*-hJo?v)vk3L16GE#?Bgj{_L6%9qAA(S~RW5mGJsvOI( zK8?K$RiT@__N85NMi&ho67JWdd%RL%4h1m&=u>1-`t?k5by6$L8JPja?5I$*KtD%8 z`cSr6=U&FWi;14Q6M7u4XCSN@vMOCb@E?rUqCJkWq2r_=T-XZ+N5ilPFxfKMI?FQ4 z+GuICuCuJO+KPG?^y%L_+-Gd>QGKTNuI;n1_uM|Kdavk1K@6bvvvvGXR`X$-8dLKx z^?nRA1RLTSLJjf1>9)0SYth#3TPwDPxAy%_rT6~y2Qy?+>f7hiaD>hsjM~&2jF&q@ z=0thZC9czC3>=k~hSQ32*G+Jcyzo!>NN+gMrTWub5B}9|i+W4GcU5}*+q0iI-E;cM zTX(;7sZ+H)z1^KU=0hoM&dHfHdD3KZoGicSPq|>;4Rf~Nd(qcA@>JBzT4_7owoN^! zK8NG<|eoC_k7OmtAPj%SCj} ze2d)>$WP3&8{+aEi3UfGJ-v|p3OnQ~Agw}gHr?(VZHn1=eKDWMa?u+K!u^LLV8?*a zp!n*naoH2{W(8-(&B&_Do}RNHdvVU(qT52QA(kQ5fuVu%wi;(+$ef<-!WDxYTc5RAq|p@Iamyr zMOEA9h)qKC32DfgFywy|qdE=2sgZSw<5Z892Bt@&R7=8aPrwvN8sb!ErW3in(;=uR z+kKPO`^9-{T_f&b_6|~UaiD6(VLAn@)-#J_)Vu24^`3ffy|3P1A6PDyOQd})wJotP zbuD!-^)2ops?Nnewhr(F%Fqq~R<@&;7-Mll7&3g3tSvSrYSLzNOO!khL zGiz+W+1HFdbghaNyOu9URHI^*oCg6&qxzEB#hXRe5$;OoY2o1&qh-cMqNf?Q!DsOrWIISxq6=wGYaz6 zX&GdROv_%#9)-+<-R2(a_L_XRG%fOwJQxe*WX+NSYnB?+EEu+^wK9fa(0dH7llofB zAqtI-X+;W*{>1sml$zF;*m6+xZhKD)8J0nkcM8Xm8K*&mvLuUG=v+jHF;VPjmd2Fz z0d!R$75Qla#h8;Nsgr-dpmld;J;)lqV%+HR$_=`cOA)q2a5MHgo|CJEXj+Mk38QhS%7@A@y6NZ$XAosz|9 z=wPR0)#z9ClQYC$T!wrHw;Qf~O&hylGo+lJm%W9bmx+|YZN^cUDOMNeP3m+p`i zoF>04Xdt!F@X--P3UazBh**x|h-HEhy=2$U4VMqkczh8S4Jsw+^z_`A?nu)L zc)C&Y8l{rxA9-I+WXpxD$TFAm*PO`1bmUeujz+$XeE*EJZH%<7{V?|ChK_GGifvZ4 zF?-DtN7q{QH)oYd7}L5p8RBTc=KBdv5^NXe0E&t*v~9qW&RH(B>Z zYQ{ur+7H2rS7RmX6V6{|#JKGSL{Vz@jz%N`_!xudAgHlIv**yCV6!0xTR7l5`Ip0d z8zEy5jn^s^8r{Vv%9!s&Sr5&*Tr}P2zF~$ITGy!lgcnQtJhN zUDzokysgYL$>qSL=s=%kl4}83e{l31hs`1hyFHj;HySZ{n+eI&ZgYV!-_T+)qKol@ zi}Nw;6w)4?w8PTEPlA(X=*a_MBPvT5XRK+I70be-9=P$bNbC63ceMVy+e#G?!;>b+ zN77nLDPs8fgWH}S@$kdX?`)K6PH9(E4Ve>p`v@4?X*eMwUCHaLD(!w=$Q_%LX zxiWsj-dBa9uXrw8nhJStG3GT0sGcK=ja3eDQ+a+>24bu@MpsIud4-~Lwp&eAN((L4 z&e?^I(#~3Tkt3L1dAY(1T{bq;f{B(w#OPox7d-<1+vBlRvh1ydRYX+w7E+LoYuydm z4LJ?D4S5Zn8m?&QJgZwnA-t{4cUT@YKWK5^ZNA%bxAhJrzuauzY%!9m^GiBuEQ(1? zdCh6pYBh>!a?vl;c;vzpsT?Ta;k_^PTBuA;XbDW3Z6U4J-b z+ewi=?l@6$;ZE`zUAdgSMrAdj(9cmla`NU(oq6roFW!#uD7URxbp70Uq@<3G2LJdR z&ZOZV@6nw}rM7RSFI9)U1Z@xsTjd7vYskUqYMMSkfC&<18gwAV^aUX=*#HTLGA6wY zqFABKQ z?8?k#7V;A!Bm9X7&^FOL(Gqtx;&x$5O=Bp1!pUHy#r#M4a+g#Qd1)6ze2ouZQ`G)7 zU4Qv&v@)|&XFxD=7zG(G{S=aL9Y5rfnLgr3{2hCZa7Vd1+>PQ!BJ;C_QczNKK1xHG zWzMn`E5&ND8Gac^M>!C2_{J&Y)N$r<^fjD*5{wL!rP2|6#w{D97x-(mcR9Vy&S*|b zY8x3z#?Urn!||83g{V^o8YF50q|Q8q^k+PhzYOye)59UFVpR#%gyvGER3-EhYB3t@ zh|nk|q2~dI@@9T(bvUl$TRC4RUK)Gnix*$njw8|)IcfqjeO}DVNys2XRO@*;W1pUO z`j!pRZ$>}=K-`a2%?2EioXHFO#CF-RU6oC>ss(Ycq&`@tMG8_*J&HFY+X`pSVg=Am zN+2fu2h@h|Bc6ZBeQAxt5Xpd@I&7Ko?DVi3E^d^c+kmfwnh3@DqEgA8 zC5V%KF0x!&DXj!N8?i-f+R6)WTsQ*sWu5=<{M%|5NEP;TVara;^OL+0cahY9CFDxz z8`rYsZMl9$QZPnXB&60a+DEg3m6t7cC>Qd$Xjq|abs-)kf z6(rkWsM9IaSCHsW=#=D5j58~lRtr+nW(MU7L%35gu_CSj1}bOHes%^LM|3cu|A{I- z@9;%^aWZ-x{EHQLj5OJ3(ik-6==0;ooTxc|PKmxa)-kV{()h;w*RwLSb5G2Fqu0XT zWjFSIW8NECS=rfdEO?`2VYqzJcx6ANDNwYX2kf>PX)o1K;Q#)8-T z+)x%?*z@%TZ{%cUMsAwBdGp-)O%Ky(1sa{akM*jAuw7QSiR;OI46(Oj9*+=R?V6js zFDuf-Dq9Cd?Wa!>+f@B2uw;$(EqY3cu6`}gk?JpGNUJ*NTr~5v4*ez#BNXbT3F3=z z9ml67PE1MWqj`2PCJuVtTF~pj4)++vY=;Z4x6B@A@VOdsTTrEpRNN6^Mdktj@ClRAmPxFV*OZm@tdH&j;4IPE} zF;f?nJ9 z5iA~E;PqbQW*#DIHmLA6#iJ>R_k;rnc00zzN}0z{C0wQ|hsB1lmMd)oS=S|-4R~Za z5sK4d_920_qBu=h#3a)Vm@811B6GDUBlzs>p+YvUMYn1fQ;-|F=+HIciRoFNWW$-WSD71Kf={sXxj&&6M z^}42^S0but=yi$e8AfrS?X<lbfP$f&5{fMq$k1W-Sd>glno?k?Fc0Wt!8&ABbPk{8GfYHj4n$X~Xi^*O zg~mY0+KP_A0a^5=BDo{8m&i!s=gxOnrMToC5QJ&L?OVQRUi0u5gNF}qxOvpDSu1~- zv-XkyIe>leh~aZ@9zAT7`+;K`C<`+{m1W+g1&te zI=jOt)EFIWB0Z93G18UXs8(Rqy?dhmRUJpJ>Lq2!Co5J4D<|y3qd%>quaM#56~)C~ zM4hAWQQLaO$|xqTLF|gL!}40Ht?0G>M1hp-t?#K>@* z!LrC?g5+IH*J6({D6nA)V-Q;?`DvUiNUIElEFsYYZF!9PwQ>UXjt0<+4PjUi<{#5b zGEXZ-=P3(IFAeEy0bgS2>s|3YWW2iiRi?s z!&pU84OlKhR0Ed~*|E`g9MUFP%vQ6_>2Z3UKBwOqa0V?F0d&Ozd|b5P##hiYE))CV zw2(-&Bw7<~iS|TCB3v;N-HDz=Z;CI)pAtw3h7vd#0B8M96f5-iN9{XdR_TP1xc6rt zEah~Sa-!?rg&bZbh)MP$Ud52NbwP@|oFezKM<)xIEgRu{Z)om%-fN8|{1aq57~{A5H7lth_!)9t9mjnp})t(V_7!w_A>B#L;tnbs>1hX+`LUH!%Gb*W}{c^|`#dV#3!aj}|FyL%nsm*0kocRlW_j$^DK zNlo*|R*##LvM}}FuU6bD8wNZ3_Zu>N-MuT988GuKA!$H=C7&&C*Win2!jJ|(G)*BX z2oDZ=)KUAYF4JPc!T(Ru9CnaBE!onlpCBFbUami$^P0i+D#l3C zPW6;>0zOk@0r+m%>NG#;SK=@TG9d8<@J$NCs|a4ssAg{kJQJxZfIg(Gh*60ZnU_ph zl(87B9QTHZ`x2bTVhLTI>>@m*yWCyrt`=woh5}=E6Q{}G1Q`YH0#AXryRW;ydw@P} zm^@4wrVcX=GY_+jw2rimv=4I(a}IM2^9`doNWo0qypw9jG(-tCR`BYT6mwFGnfSz( z{7*cxaKR&wELiwRgLJuyyN#gNJ@b~v#j}@giRz1`+PVaVJa^z<`~!RE0moheUzDg&x$LmuL$Oh>{LD| zlu|UlQ~d&)ZI#Q_yGPRCQnbWM*pc7PcH~CHHNbo_M*kO$v#B9q%d9s9zVh} zBFqYeg3t<_kt!8Yno{Ld1#6*Fpy2Hjo!V-lF&(x?CLbf!PLdeH~x9d`<{MD!w0=9le0R3kU@e?V81k=c7 zD}Qj$&h^W`xKsUFx{;+&8%)!XbB{cz>cS-$wVDxThw@ND1k1uq>jUO8jH)ocHV6Z9 zBA9?JcsSBL46R*IS~3R(>+DK{Q7jR#cs_$4?IQ+?^TCFUq^v7hL5;=;UQ8TFb;p-U zwLK$4>M0xDHS@QZUO;^QHBHN2YW?~Nc|uj=JhT*guDlK-Y^%5~T!e`q@;AkF7H3Y0 z`J7^$%=MdWz_b(6&rw!JMHM2;m`OQ1ISLjHZP1=)F0Ggzg9IAHTst&~{nM0swbsr9 zTugOfwvTGL`niScdwjCxx!c$D_z=Npw}lr^zwm|pLhr?OG;_pxl~_Y7Mt`C;3={lI z$OD|>pcSx3F36Csh)=n#GGF z_nwPXtvQK+8kH1zYC7_|j+GX)=XE_8d1UELOXU|fNT(x78(Lc=J-e$+nM^tQ zev0TSUJTbJA;VQ-LL%~8h2ldnV2gtt1^%F#81vo2_#ZxFNxMosX z1R{nQ1(16#IvUdeoa`VZP0`&!nKYvIJwFLS&Cx~LIZD*lBS*{0$|(whio9K6krTD{ zgwdMvL;nFIhmHN$hF8vvshaTZI$v4tO_4`ar9taHsFg-#M2^H=F~0M$$g1W0&&I2Z zrhjz9+SdAoH+}W;jc2AUyh+pc&8nI5>c-jE)J5vuuE^W}xZ`fA-`W1xUz{5yojo=; zs?ywO`+O=zC5X+ZuBZ7_^fQcg)Gg>|HsKfZ!o3|DVxHTvsEE}G3Vx1Xl-&rF3=d7m zBw?Rq*rg7Xq3_xan!y9=MdBDwq6HHa34v&wYT_>XY}eUKQl7N;u-HJFdK%47AtN){ z#IS1vC)#2foGUJG0wXlp);hTV9E?TVtYgn~5n&(sAjyT7};wOQhqG9!sR7((xsc9@25tXpP(?AJIO6J|C0l8Z1@=BK~D&t_b*1Rx{5_9QbP?CE*US(qkmfU(}sA_Gv@xicK8F_X79BFLV z>fGXf>6wj*iSsA+AzdU*UN3(OT;;-gtCKmgDcRfP*^?Y-a^^OPJy}i8-AZ*HWQZ4y zk_-M7>Ss&uXYe41DVrygs~3&^^vvO}&}f!S}uA0HixA=Wa65=P1=~Hq2;Bb!~;FQo=*1&pT2fi>zd;>l% z@b?9!2mTr*v%0ReK6=L^KX-@tXY`J=_Gf(i3(w$;3Q6s41CRd^~cF#v|uwyx)ag&lls5_sbHkX2tsx=nf>0$xR7( zHpID}%B`}ca1I&Y6-Pd^T!cX$fz;jFbMBXH?g0u->?q{k#MFD{WPa4*Y*^XkF zoRHFbT3)rCIN$%ZHHWqj&}TjbOfl$T}PkYdj;=T*m# z?wry&D>EZC$!WJJF0;dwodk7I3IySK;&7RgGeuHo2!TUbCMQQ)n%`p(9ayoV@Y!c1 z&tkLje}09;L)hP7Yp^#s8k`NThNOn%hLncXhBV}og_STC=!{^12v!HkTtiQYB;fZ@ zgwS+=9)XDk^~1C)AFsXp{Xw#+M+A%Zs`NrhWU={?2WG!GJ*VcCDW61sw7gQXQFB>$ z^?YE86#i_+^IF}z(`sIRv9WM^N#o0Ztgih>QFkd*k{U~9bZeA+l5-G7Ee&ul{zChJ zM=g89URA8aaOJ;yZimyu6pk7W*Q01wNE>z0;rbeJjdYEA4T%mC9%O(O^E;i>?wQAEAR4L;~$?v z-Z-9TWaZ{iLD-BDm1LEz@XMw;lBULyS~(#PB2%LpmrrYj%@Ax7^5G~dO?}yT>Wiri z94t0Xh0GvMDMh46okS-jH7}Nm#a(C5@#8yAV6H!bTWRfn z0f#odnV2ISqN%8#$FomCeiG<9z^MXsE5NDppKyvVA|=NC^DS#%C>Bb$iM2ra8hQ++ z^jmZ9od*fk82F=>v?B4E_5Xogbez}O9@ z!#0zKNW5m%My9@pUZmj{%SG2&>br>4(qZfc3CN=?a=7Q2V8%M*-$#vr3HDfY8wcy9 zNbeKVOW~lJEF~BcjCsh>m@joPbTQ`J^6kaSK=|(M6i(s^L;=jVc=V>PFF3>sh$VWE zKJiN-4oG{?FlN9*SDqAEER~FT>^h{(&RU-psSRm$ktuRw>u0kM99V@mS*;nAAJ8Ut zte_!{fck{hVKm`+kb(L<_=Q6}{d$oyFRwY*3(N^$qE{r!c%&j-a_D3ztzK*!QB{3? z(&F^zRxG(w`=NJnN%_2$ONc9W1HaGGK13h9KJ3xg3$S1S{`m6?2JPuYV=ey^Ze_Vr zx>A;j>B~C9xBlXyz$7dRXdk}DdLv%Pf5|cjM^Zd?%zEo5@mFE(jBjZNxTsK~Mx-jB z5VlU2+vD~51Bl=jpOA>TBvRTkv$At?^EzG8xl7mlf^LOH-Fp<5^eipwRo=T#MYwNe zzy1RT4jMdU=&-8cBSwxIJ?6@>^Ti{ z=gnWRaM2BmZ(Opp@up=rFJH0pmQ}Z|zHQCg+wWL+=lZ+u-mvjk_uPBm{SRz!aI#z5O?jJ^sX#JAS)!*X}(}J-zpL`<~hV>~qf_Xnx`M2mkQmOMiU%(BW5( z{ORbiz@>g&W*u>qrt<6@fllQ<^c5U+~Y#OvaJ#B}kNcvGAZ zd&CU!53y0aE8Z6Gh-*cQID;6G*NK^8mY6NB7jwi`M6H}B=8E}<+_*?A6gOb3@TIs> zEEP*cqgWZ81rmdS9jp{ziNA{jq7~LB`qn0XKwQcn@$IL@XW~7~5C27cC{Bt` z#QWl7u~+;qdf$HWtT=?@ec~B$7=LdOe-vw?*M2KDMUS2kn{o7@_?myepr4f_@tf%N zUkN0?8SNMuZc}!P!-jq0ZX~1cZ`>>n7r$`)z+}xt1w5?Qy z=#A?i;C_RAT1>{@#q!>^>u~>c?Dg19*j4OV*z>Tj#C{Naf9#Xwz2Z9TGCd=Sj@5C=!zmswj~s(;j(BtHAk1IR6Fixq!b_u&aEV z|Blt}#yOv`si(yR@GU%3Y&Cu<4r`5VPvG22{CykWG6r?njqlk@bx=jy8K@Gg)i1qn;5p@(HmH$CU_}HdrpDy)6U-MLsqj7lY}%`Z?X-c8zL9o$A$bN1Ik-e;M`JguNS%L+W!<5stTN7Ex;wQbKgkp`6OV)e@?= z@p#*ILsi=W!w&ILtS$L_f?~23Z2KG*o!4shA_UL%CUt+?Dc~hvIV{G|Zb%X%jNjwi zKF3(ALR6ve?dGx}(Hmt?@bRR!bLux@Iqm2lt5F{j!xgo;>26VDD#f>nwr@??)hy)Y z&XRocI?0CJN6&zlm4We&wwJ0~zrZtUlp?X5cI^tZEq0Xu2>tj=>eFf#l7qh{4)A-Z zEvQ}SFLu5EUHnURi0$0hsJ~&Sb^wmXU>}8j8TQSFVWFaKEJ%}J-JF` zpzqkI-%$UgK0^Hi{a&EmCR4xVZ}^D%cH24nF2g=41e~qGJ`{VJ@g=b)x~CDnP@kpx zwm%nri~Du#FZybfe!m31K?71lv~8`)2pkxC)`dSNA_H+;n_pt<#h zr%FFWRUUxo#e-mlgxE>2q^lCc;RQcZj1r>}gZD}?R*b_O(s)F}MN}nm71leh#!AOE zqE|fF$`9WD~eAB-aDr+Yf?cKO~wUy|#!)AWI&Fbo#Z} z4vzhpK<;_Sv>oENkaxSpZpfmi#M6Rg0!aaq2+xVE76Ya9!M_xNky(Lm$Rp6aQYqDi~K)C%%(ZoAhAv z_~egL3RB)mot}C!tugIf`kM5AXPn4Myz;yudDVGS^XBF)%ey1*fxPW`Pv;%X zJKo9DX?UlPuNZm7x1Bwmr+4Yu<>RgmUH4&NHa-8h1>L)Oy4}!iSGTi;WrZIVU0d{W zcYF8z?hkkWdyj@5zc2Qe*h}8-IkV^fo^O>(rK?LLWtnAD${sHJC;mlxS$oCxdZ>It z`TX)b%fIVAsrP-oPxNl!XPrp7ZE9?~o6=N#?9bOmyzHdU`0e$bP>|WWoazy1- zmDg4-LXgF~E1N2xtlVGua^;(qA62$g{<~l2eslYW`cLft#{u2}+XuWquQ)T1dGq4lL7SA#~&1ZfD%o9$`(Bz@)20%9-C!tn( zBAH=k+gcn?K%MhM9UtG!$IBV6V7QXuF21^(;Sq*M0lVVKg@D;@M*#~N_C&3^ivDSTuX3}-OBmhYd*_gu$t7Q^cq&Sp4=;arCE7|v(7fZ-B`OBpsYyoup5 zhBxzDmNQ(za3#Zy47c*zA4R0Yu3`_Ld5YoFeC7Zjzsc|f!?zf|&86O9_%6e*`22Z> zt$_K!%oM=FwwC})+7Ji+iShAn*lE8J5koOoBEa5JQyRtPyruor4rgud}NKrN9GSoyk|PV>pZ9^$ce-oWpP~mzu|LKEnkJmoQw)u#w?S43{yyncuRU;R=Q; z8E!=%E)|dRJ-fILyBR*sXO8gkQNS{M-9$j5f--y^9aE1g!xwD;9EYcsaqlX_SJ0X3 z7|vpNJ;T`y=P=yLkosYnpng~ejz>>;mLc`YGC_T^7v9((FdOH4f%={Wtmoqye7up5 z_wX@MdN1)NAD>|O7Q^#=Pb*+K^UrehqB7{(!`b#^KKX(449N zOyr(33CGKzNxK0j<9G_73oSJnXGo(ad0NNvw2tLz9do%lmdJH1PwQBo*0DUTV|iN7 zaPck$I-T(@0Zw_RMfU0kF<#k+R>}vg<1-r~Z{v6uAMa+^%vX=_@ll2? zB!Tz|`}qm``3d{^3Ht>-VLv}%KWNjAV|v1V^cFg%C+vr&pO3KwwcP=3y93;I%}ig- zOkd4RU(HNk%}ig-c;gg2i)gu->8qLPtC{Jmndz$;TrmY#iN2aqAG)9DtC{Jmndz$; zyoIh3eKiZBuVz8?)hvj|~65`8rbqOWE_^wrGt)y(wO42;p8 zL|@H<=&M-}eKiZBuV$vNL%<2m*b#p_B)T&!!P5?jQt+rl=o_;D%Okr0hw#;jpsYiX zq;&se=r)IdB|0XqcnEbq3P@b>5b8$o0N>NhPk52fyu^^WqJ{7Iitj(q zXIcRda}5u34G(h-52N>{;5^mvFxT)f*YGgc@UWPOIv?g59!3o};FxN7m}_{LYj~Jz zc$jN=7&WAOsD_6H)$p*O8XjhTewb@`m}_{LYj{|E#rIPU4+DqKV*E<9d4#F#2-C|E zrk5j3Wk;CGjxd!SVJbVqRCbh09pzF-xztfEb(Bj%ZiDX~;HxM2>RWvEEx!5|Uww zP={TBq-UODJ@XVWN%xSRc?y^$NP6Ze^uYdrq-UODJ@XXmOvj{Wo)V;Io&r|sPSP_^ z0jmT_&pZXJJ`27{8sKTx08b0j06#~qHe-ZO`e6&xcni~b3)6TD(|8Nhcni~b3)6TD z(|8Nhcni~b3)6TD(|8Nhcni~b3)6TD(|8Nhcni~b3)6TD(|8Nhcni~b3)6TD(|8Nh zcni~b3)6TD(|8Nhcni~b3)6TD(|8Nhcni~b3)6TD(|8Nhcni~b3)6TD(|8Nhcni~b z3)6TD(|8Nhcni~b3)6TD(|8Nhcni~b3)6TD)A(1+Ex%%J`4w}^GdME~^Cd3u@H03w z3$ltZ|084mN5=elmTTu(uAOJOcAn+hd6sLfe6^LYw(`|hzS_!HX?{&$o^3eh*5FHv zxi(5`X~f*UL$&eR7_J*_f>z5V}ud_sF=;#khOR7WKfEhYf8>GVNCQ_;Y zm9G?c%hM5?%xX~J4TQj7D}>nN5~;fF7Rpx6T(|(wOFb_ed=a0t**IIm>QDO{@a{^H zv=`z0YlW0C752iSdfNOcS5BF;U}PQc!-n;uw&J$7NO57$Aq52jmFEqsMUo27_~qc-`Iu(N+zuM;wS7!jYeG3OhzMRhrvdtRdOaLSABy4#S}$T=qv(s zsJLIl6$Jq&uu(EW-f$z`*YPD>9_P(wI8K<%M!XVbu!=~w%|@iOMD6$~Tn~#`!)>S- z)){4ks^U((*2WK~=NkD{u#0ZrP-)zQ3gAU1ld7tCkr5TcS^T4K;tgR3(1M7c3Va}e zv&96o03S%0%+HR#fbe0#JE%%PmeF9sA#7*yg^1e)2NWG2xEnP?QH0~CW8i~6f$>2# zMASNdpbmZpAGlz#Sbz@;p@WLzJ*cP262%9~Tkuj7f@0$~-~$-Ehz|!|q2n2a@xl)H zpf5+J0z48M;{z{(#|O?*88R!sh!3mN$oMcD5#tNUHUoVyCXQ{4OM{ha2&(|bhZ!x1 zAa-a)D^Nq&=GHeFQH=0mrLr3AmyBk@0~(kLo2UqF_$0cI+mQ~j>AlBjv|6p`Bv!N@ z+7CApQZy5ieIYq8JuflGr8zwtfvEacwC5q{${ zZ8(764LSts0<&xrzSBtfK$ikt!FUy~RP_cYEMD9$;)C8qt%gUKY&P_Ii_L^rqLUa< z0u?nQP?3h8u;(^XJxqxCjoVN$U{(gL;!fbhMVF{`sPaHb2YkS&x*Z>QHu?fy1n(f~ z=J-dvf;aRxA?zL#;{!>QCK1?=L7UuQ{4dp<5D8e@NLevz+fJ)Ut zMS&08Pamb3Z76L<@@V~$)Obvk`cHyX9t34}I2`B$4lA0^hBIc+Bk#xJjY!)(D|yv)vAS;6*l=P@%!Bzz0zyZRl_C84XyX z_;5z?0Uj2^1`!+TW_IBP2r*%^X~43>MzK&(F{{gpkX?)qpvM9)RH7#pSTe)a!$9-| zIsrbY#jv`fKb31mbguUoIvK@>#pMDQu)7Eygb&~ywY0g+X2u6z=>jPsD2xfWp<+Pc zMSKKM7v>FAVW6Y~KJ+iws0~pk!UtZ2VE4o_t<<6&@!<{>jrnw`rOKy^5GO zUZQE{pbb5cctaE)9cUimB0+70Q@oBy(g=Lmt$fyo3gUn_^fw_qaaLpmFq)iJw}bG3 zB=U$z*S>MzvUpGp*yA}IU=|h^4&h>Gwc9+X1MuO*_t@|`1}plHMWeGY;bVN@qYPFj zA=6GpXk!dey{T908{-3q9*+k!;c=ilJAn^y65s=NtZ=fh;wiWR_;7f=X55B~0gHqW z@ES0YxORL1ndl-v#RnnOYI8b)4oQm#i)j9}->I9CokWW(Pi8pdIuS8)}AD*Su8JWHAywp%=lsiFz>7YUq!| zjdUL~YW)m44$9+Gy0Qm9REGGPP;gw#n7N^f=!ELA*T7iTa?gSHw zM_rg#5_14bI?z1Qs^hy2XcoMKNYVtvJ5e#5wNnp|_FVKgA$&-ZP55xzy)G~miyQbb z;sc@^76a&5tge5x|815%JG1}eK zVooHJ=M8nz>rGUSo`Vg{67{m%efU(1+v`H}xrr#96c)$nvsq2}>Bjv&kdn*qx8OE| z!3``b;N`dz_(*V|2NG`}<^YtCvK!s3AQhx=MQ!jpHxj*=fOwak&pJ>+osx)w10ViG zyMX++cx4{Jxe-|q)a_}qA<2WK2Ea4Cz+Z?~H86UoW!a87Z! z0|6^;L&bnX75D(JffPz?#|Mx}_|Qp_X#|Z(_~3VYJirGKPxt^0LYSG1QA&nBAw(dV z@L{%i9DX-Mq>c|XXmn$I*aCQg+X37XKG1%^hr{kdR1>=sY69T{J;i~V;VTVe{_9*YfH{mFNPqiW&E#U)aoNf|h?ttB9#!sZ_bp*g# z+;KrGZbQX@MN)Qjd?Y&^xPhJve4vY5f)6~)W<(#u>xe~}fq1vWKxduQ!5t z-s7aR5bhZGL@62igb;D54gt1d^*MuH2ppS_)ZQ32NIyc~vd6h#w~O!dKvdcN92ge4akw+oSiP!W^MPd(V{i{gWO1hpe^JtxZ& zyB+!fjzXajcmz^iW5XG@m&BMi1bpD9A59PftMw(s+i@EzhE`A^+i)kCYPuT~iT4|g zKqldXs6*czXhb*T1Fr)&wpf68uZzw)+|q_@D#cAUx1cZdZJKyxkrM`OuvMIOFk=jP}L592Wcx;B(`_T6~EKcHCw%1%N^o zvJH0v9~r0%%Tnsl=pr5P;WWAt57CTn3iI?p0QdmneQtaT2p<(B=|vmkBO%izNS_M2 zs6vGDTi|AX91due6Zu@}`k2C1=yZl}>lqbRK_JRuq!SvBn+^8A4mMNY} zgQ2@!0en615%hSxZlB+$KNUB!%rogf2~v45Tz5i30(e9`%;|$coIw+iOi6(5gr7lh z1$Z-h{7H!p+=hyw6`%}5t0ktI=>dl#SxOxmC;@YiZBBf-*I`CCMN0*P5II1+pYY)Y zSAzN*LCm{fVqvHb&8r>Kl&P1=*7x1_Oe&E9u=MT7%mEG<4CSJsc z2jpWRS>`e&pk^MM)9P{KE1Vv)Cy3G>e}KpaTRT38>$#({1o8w&iHV88M*^V(ICOaZ zF34zqqT6Z3Pecnvge|W(keq~gTi~NXU{Ql?!<}HN*Q*^l;|15L1K?Y;ciBq=PoyO9H<{8~lE+ahCr}q+q~@?ZDn@wz5C) V!o5>kzlbDRJjU5v3Ujx#{eMf40U-bY literal 0 HcmV?d00001 diff --git a/src/BitstreamVeraFonts/VeraMoBI.ttf b/src/BitstreamVeraFonts/VeraMoBI.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8624542ed208db88b2d1e99bcce1e1acc7e6c28b GIT binary patch literal 55032 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 L*;UZpvd{ko7U7TT literal 0 HcmV?d00001 diff --git a/src/BitstreamVeraFonts/VeraMono.ttf b/src/BitstreamVeraFonts/VeraMono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..139f0b4311ad2e0369a347b3be6c46e6c2b730d5 GIT binary patch literal 49224 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 literal 0 HcmV?d00001 diff --git a/src/BitstreamVeraFonts/VeraSe.ttf b/src/BitstreamVeraFonts/VeraSe.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4b4ecc66671e45e9dac162aa85dab558cebc191c GIT binary patch literal 60280 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 + + + + + + + serif + + Bitstream Vera Serif + + + + sans-serif + + Bitstream Vera Sans + + + + monospace + + Bitstream Vera Sans Mono + + + diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 19b2aac..0a90333 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -180,21 +180,21 @@ if ( !$content['error_occured'] ) // Setup the tab title $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); - $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + $graph->tabtitle->SetFont(FF_VERA,FS_BOLD,9); $graph->tabtitle->SetPos('left'); // Set Graph footer $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); - $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); // $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); -// $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); +// $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); // $graph->footer->right->SetColor("darkred"); // Setup font for axis - $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,10); - $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,10); + $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); + $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); // Show 0 label on Y-axis (default is not to show) $graph->yscale->ticks->SupressZeroLabel(false); @@ -225,7 +225,7 @@ if ( !$content['error_occured'] ) // Set label properties $p1->SetLabelPos(1.0); $p1->SetSliceColors(array('#FFF584','#CBFF84','#FF6B9E','#FF9584','#EAFF84','#7BFF51','#51FFA6','#51FF52','#6BCFFF','#5170FF','#519CFF','#EAE3AD','#FFF184','#8584FF','#E698FF','#C384FF','#FF84EC','#FF98A3','#E5C285','#FFDA98' )); - $p1->value->SetFont(FF_ARIAL,FS_NORMAL); + $p1->value->SetFont(FF_VERA, FS_NORMAL, 8); $p1->value->SetColor("black"); // Adjust other Pie Properties @@ -250,13 +250,13 @@ if ( !$content['error_occured'] ) $graph->SetBox(); // Box around plotarea // Setup X-AXIS -// $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,10); +// $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,10); $graph->xaxis->SetTickLabels($XchartData); - $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->xaxis->SetLabelAngle(0); // Setup Y-AXIS - $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->yaxis->scale->SetGrace(10); // So the value is readable // $graph->yaxis->SetLabelFormat('%d %%'); @@ -265,14 +265,14 @@ if ( !$content['error_occured'] ) // Setup the tab title $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); - $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + $graph->tabtitle->SetFont(FF_VERA,FS_BOLD,9); $graph->tabtitle->SetPos('left'); // Set Graph footer $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); - $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); // Setup the X and Y grid $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); @@ -292,7 +292,7 @@ if ( !$content['error_occured'] ) // Display value in bars $bplot->value->Show(); - $bplot->value->SetFont(FF_ARIAL,FS_NORMAL,10); + $bplot->value->SetFont(FF_VERA,FS_NORMAL,8); // $bplot->value->SetAlign('left','center'); // $bplot->value->SetColor("black","darkred"); $bplot->value->SetFormat('%d'); @@ -328,9 +328,8 @@ if ( !$content['error_occured'] ) $graph->SetBox(); // Box around plotarea // Setup X-AXIS -// $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,10); $graph->xaxis->SetTickLabels($XchartData); - $graph->xaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->xaxis->SetLabelAngle(0); // $graph->xaxis->SetLabelAlign('center','top'); $graph->xaxis->SetPos('min'); @@ -338,7 +337,7 @@ if ( !$content['error_occured'] ) $graph->xaxis->SetLabelAlign('right','center'); // Setup Y-AXIS - $graph->yaxis->SetFont(FF_ARIAL,FS_NORMAL,8); + $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->yaxis->scale->SetGrace(20); // So the value is readable $graph->yaxis->SetLabelAlign('center','top'); $graph->yaxis->SetLabelFormat('%d'); @@ -353,15 +352,15 @@ if ( !$content['error_occured'] ) // Setup the tab title $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); - $graph->tabtitle->SetFont(FF_ARIAL,FS_BOLD,10); + $graph->tabtitle->SetFont(FF_VERA,FS_BOLD,9); $graph->tabtitle->SetPos('right'); $graph->tabtitle->SetTabAlign('right'); // Set Graph footer $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); - $graph->footer->left->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->right->SetFont( FF_ARIAL, FS_NORMAL, 8); + $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); // Setup the X and Y grid $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); @@ -381,7 +380,7 @@ if ( !$content['error_occured'] ) // Display value in bars $bplot->value->Show(); - $bplot->value->SetFont(FF_ARIAL,FS_NORMAL,10); + $bplot->value->SetFont(FF_VERA,FS_NORMAL, 8); // $bplot->value->SetAlign('left','center'); // $bplot->value->SetColor("black","darkred"); $bplot->value->SetFormat('%d'); @@ -389,7 +388,6 @@ if ( !$content['error_occured'] ) // Add links $bplot->SetCSIMTargets($chartImageMapLinks, $chartImageMapAlts, $chartImageMapTargets); - // TODO: Make Optional! // Create and Add filled line plot $lplot = new LinePlot($YchartData); diff --git a/src/classes/jpgraph/jpg-config.inc.php b/src/classes/jpgraph/jpg-config.inc.php index dbf2726..b148bf8 100644 --- a/src/classes/jpgraph/jpg-config.inc.php +++ b/src/classes/jpgraph/jpg-config.inc.php @@ -38,6 +38,8 @@ //------------------------------------------------------------------------ // DEFINE("CACHE_DIR","/tmp/jpgraph_cache/"); // DEFINE("TTF_DIR","/usr/X11R6/lib/X11/fonts/truetype/"); +DEFINE("TTF_DIR", $gl_root_path . "BitstreamVeraFonts/"); + // DEFINE("MBTTF_DIR","/usr/share/fonts/ja/TrueType/"); //------------------------------------------------------------------------- From 24fe253c4c63eeabb8f2d9c843750c7cee7bd99a Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2008 13:47:10 +0200 Subject: [PATCH 108/142] Made some tweaks to the charts --- src/chartgenerator.php | 60 +++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/chartgenerator.php b/src/chartgenerator.php index 0a90333..453ce2c 100644 --- a/src/chartgenerator.php +++ b/src/chartgenerator.php @@ -184,30 +184,27 @@ if ( !$content['error_occured'] ) $graph->tabtitle->SetPos('left'); // Set Graph footer - $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); - $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); - $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 7); +// $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); +// $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); // $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); // $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); // $graph->footer->right->SetColor("darkred"); - // Setup font for axis - $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); - $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); - // Show 0 label on Y-axis (default is not to show) $graph->yscale->ticks->SupressZeroLabel(false); + // Set Fonts for graph! + $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); + $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); + $graph->legend->SetFont(FF_VERA,FS_NORMAL,8); // Create $p1 = new PiePlot3D($YchartData); $p1->SetLegends($XchartData); $p1->SetEdge('#333333', 1); $p1->SetTheme('earth'); /* "earth" * "pastel" * "sand" * "water" */ -// $targ=array("pie3d_csimex1.php?v=1","pie3d_csimex1.php?v=2","pie3d_csimex1.php?v=3", -// "pie3d_csimex1.php?v=4","pie3d_csimex1.php?v=5","pie3d_csimex1.php?v=6"); -// $alts=array("val=%d","val=%d","val=%d","val=%d","val=%d","val=%d"); $p1->SetCSIMTargets($chartImageMapLinks, $chartImageMapAlts, $chartImageMapTargets); // Set label format @@ -252,27 +249,39 @@ if ( !$content['error_occured'] ) // Setup X-AXIS // $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,10); $graph->xaxis->SetTickLabels($XchartData); - $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); - $graph->xaxis->SetLabelAngle(0); + + if ( count($XchartData) > 5 ) + { + $graph->SetMargin(60,20,30,80); // Adjust margin area + $graph->xaxis->SetLabelAngle(45); + $graph->xaxis->SetLabelMargin(2); + } + else + $graph->xaxis->SetLabelAngle(0); + +// $graph->xaxis->scale->SetGrace(30); // So the value is readable // Setup Y-AXIS - $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->yaxis->scale->SetGrace(10); // So the value is readable // $graph->yaxis->SetLabelFormat('%d %%'); // Show 0 label on Y-axis (default is not to show) $graph->yscale->ticks->SupressZeroLabel(false); + // Set Fonts for graph! + $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,7); + $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); + // Setup the tab title $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_VERA,FS_BOLD,9); $graph->tabtitle->SetPos('left'); // Set Graph footer - $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); - $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); - $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 7); +// $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); +// $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); // Setup the X and Y grid $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); @@ -329,7 +338,6 @@ if ( !$content['error_occured'] ) // Setup X-AXIS $graph->xaxis->SetTickLabels($XchartData); - $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->xaxis->SetLabelAngle(0); // $graph->xaxis->SetLabelAlign('center','top'); $graph->xaxis->SetPos('min'); @@ -337,7 +345,6 @@ if ( !$content['error_occured'] ) $graph->xaxis->SetLabelAlign('right','center'); // Setup Y-AXIS - $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); $graph->yaxis->scale->SetGrace(20); // So the value is readable $graph->yaxis->SetLabelAlign('center','top'); $graph->yaxis->SetLabelFormat('%d'); @@ -346,10 +353,15 @@ if ( !$content['error_occured'] ) // $graph->yaxis->SetTitleSide(SIDE_RIGHT); // $graph->yaxis->SetTitleMargin(35); $graph->yaxis->SetPos('max'); + $graph->yaxis->SetTextLabelInterval(2); // Show 0 label on Y-axis (default is not to show) $graph->yscale->ticks->SupressZeroLabel(false); + // Set Fonts for graph! + $graph->xaxis->SetFont(FF_VERA,FS_NORMAL,7); + $graph->yaxis->SetFont(FF_VERA,FS_NORMAL,8); + // Setup the tab title $graph->tabtitle->Set( GetAndReplaceLangStr($content['LN_STATS_CHARTTITLE'], $content['maxrecords'], $content[ $fields[$content['chart_field']]['FieldCaptionID'] ]) ); $graph->tabtitle->SetFont(FF_VERA,FS_BOLD,9); @@ -357,10 +369,10 @@ if ( !$content['error_occured'] ) $graph->tabtitle->SetTabAlign('right'); // Set Graph footer - $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] ); - $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 8); - $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); - $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); + $graph->footer->left->Set ("phpLogCon v" . $content['BUILDNUMBER'] . "\n" . GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); + $graph->footer->left->SetFont( FF_VERA, FS_NORMAL, 7); +// $graph->footer->right->Set ( GetAndReplaceLangStr($content['LN_STATS_GENERATEDAT'], date("Y-m-d")) ); +// $graph->footer->right->SetFont( FF_VERA, FS_NORMAL, 8); // Setup the X and Y grid $graph->ygrid->SetFill(true,'#DDDDDD@0.5','#BBBBBB@0.5'); From 3d55a1b8a68a74170b6e0d3a2484932bfae1775a Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2008 14:08:13 +0200 Subject: [PATCH 109/142] Added changelog entry --- ChangeLog | 6 ++++++ src/include/functions_common.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 1ae9ad2..a306124 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,10 @@ --------------------------------------------------------------------------- +Version 2.5.8 (devel), 2008-09-16 +- Added Bitstream Vera Fonts into the package which will be used by the + chart generator. So there won't be a problem of missing truetype fonts + anymore. +- Tweaked the visual appereance of all chart types. +--------------------------------------------------------------------------- Version 2.5.7 (devel), 2008-09-15 - Added Statistics page for chart generation. The following charts are possible right now: Pie, bars vertical and bars horicontal. diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 8646f39..7fac042 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -66,7 +66,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.5.7"; +$content['BUILDNUMBER'] = "2.5.8"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! From 639c470256bdcb0a25ce03a27157913ea1a3bb6c Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2008 14:08:59 +0200 Subject: [PATCH 110/142] Added bitstreamfolder --- src/BitstreamVeraFonts/Vera.ttf | Bin 0 -> 65932 bytes src/BitstreamVeraFonts/VeraBd.ttf | Bin 0 -> 58716 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/BitstreamVeraFonts/Vera.ttf create mode 100644 src/BitstreamVeraFonts/VeraBd.ttf diff --git a/src/BitstreamVeraFonts/Vera.ttf b/src/BitstreamVeraFonts/Vera.ttf new file mode 100644 index 0000000000000000000000000000000000000000..58cd6b5e61eff273e920942e28041f8ddcf1e1b5 GIT binary patch literal 65932 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

    B-* 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

    iNMQtk4J2-M9M;wa-c_Ho$tiZ&$vDhb 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 ZpvJ#@o~VO Date: Tue, 16 Sep 2008 15:58:49 +0200 Subject: [PATCH 111/142] Fixed a sorting bug in the disk logstream, which removed numeric values within the charts --- src/classes/logstreamdisk.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 7850325..7d20958 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -606,7 +606,6 @@ class LogStreamDisk extends LogStream { else // Just copy the value! $myFieldData = $logArray[$szFieldId]; - if ( isset($aResult[ $myFieldData ]) ) $aResult[ $myFieldData ]++; else @@ -627,7 +626,8 @@ class LogStreamDisk extends LogStream { } while ( ($ret = $this->ReadNext($uID, $logArray)) == SUCCESS ); // Sort Array, so the highest count comes first! - array_multisort($aResult, SORT_NUMERIC, SORT_DESC); + arsort($aResult); +// array_multisort($aResult, SORT_NUMERIC, SORT_DESC); if ( isset($aResult[ $content['LN_STATS_OTHERS'] ]) ) { From 810b496f71f3a4471590b61f3080d9f047a9595e Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2008 16:30:40 +0200 Subject: [PATCH 112/142] Correct error code is now returned if no record is found in the Counting function --- src/classes/logstreamdisk.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 7d20958..c9a721b 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -638,7 +638,10 @@ class LogStreamDisk extends LogStream { } // finally return result! - return $aResult; + if ( count($aResult) > 0 ) + return $aResult; + else + return ERROR_NOMORERECORDS; } else return ERROR_NOMORERECORDS; From a284abdb64ca2db58e72c6db66637963df8af97e Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2008 17:55:44 +0200 Subject: [PATCH 113/142] Started implementing parsers and definitions for weblogs like from apache --- src/include/constants_general.php | 2 + src/include/constants_logstream.php | 70 ++++++++++++++++++++++++++++- src/include/functions_common.php | 5 +++ src/lang/de/main.php | 8 ++++ src/lang/en/main.php | 8 ++++ src/lang/pt_BR/main.php | 8 ++++ 6 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/include/constants_general.php b/src/include/constants_general.php index a9ef9e1..c0eb746 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -178,6 +178,7 @@ define('IUT_IMAPProbe', '21'); define('IUT_NNTPProbe', '22'); define('IUT_WEVTMONV2', '23'); define('IUT_SMTPLISTENER', '24'); +define('IUT_APACHELOG', '10001'); $msgtype_colors[IUT_Unknown] = "#D0FBDC"; $msgtype_colors[IUT_Syslog] = "#D0FBF1"; $msgtype_colors[IUT_Heartbeat] = "#D0EEFB"; @@ -200,6 +201,7 @@ $msgtype_colors[IUT_IMAPProbe] = "#D0FBE8"; $msgtype_colors[IUT_NNTPProbe] = "#D0F7FB"; $msgtype_colors[IUT_WEVTMONV2] = "#CCE4D2"; $msgtype_colors[IUT_SMTPLISTENER] = "#CCE4DE"; +$msgtype_colors[IUT_APACHELOG] = "#E1FBD0"; // --- ?> \ No newline at end of file diff --git a/src/include/constants_logstream.php b/src/include/constants_logstream.php index c7fce2d..202af10 100644 --- a/src/include/constants_logstream.php +++ b/src/include/constants_logstream.php @@ -58,6 +58,16 @@ define('SYSLOG_EVENT_LOGTYPE', 'NTEventLogType'); define('SYSLOG_EVENT_SOURCE', 'sourceproc'); define('SYSLOG_EVENT_CATEGORY', 'category'); define('SYSLOG_EVENT_USER', 'user'); + +// Weblog specific +define('SYSLOG_WEBLOG_USER', 'http_user'); +define('SYSLOG_WEBLOG_METHOD', 'http_method'); +define('SYSLOG_WEBLOG_URL', 'http_url'); +define('SYSLOG_WEBLOG_PVER', 'http_ver'); +define('SYSLOG_WEBLOG_STATUS', 'http_status'); +define('SYSLOG_WEBLOG_BYTESSEND', 'http_bytessend'); +define('SYSLOG_WEBLOG_REFERER', 'http_referer'); +define('SYSLOG_WEBLOG_USERAGENT', 'http_useragent'); // --- // Defines which kind of field types we have @@ -136,7 +146,7 @@ $fields[SYSLOG_PROCESSID]['DefaultWidth'] = "65"; $fields[SYSLOG_PROCESSID]['FieldAlign'] = "center"; $fields[SYSLOG_PROCESSID]['SearchField'] = "processid"; -// TODO! EventLog specific +// EventLog specific $fields[SYSLOG_EVENT_ID]['FieldID'] = SYSLOG_EVENT_ID; $fields[SYSLOG_EVENT_ID]['FieldCaptionID'] = 'LN_FIELDS_EVENTID'; $fields[SYSLOG_EVENT_ID]['FieldType'] = FILTER_TYPE_NUMBER; @@ -173,6 +183,64 @@ $fields[SYSLOG_EVENT_USER]['DefaultWidth'] = "85"; $fields[SYSLOG_EVENT_USER]['FieldAlign'] = "left"; $fields[SYSLOG_EVENT_USER]['SearchField'] = "eventuser"; +// Weblogfile specific +$fields[SYSLOG_WEBLOG_USER]['FieldID'] = SYSLOG_WEBLOG_USER; +$fields[SYSLOG_WEBLOG_USER]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_USER'; +$fields[SYSLOG_WEBLOG_USER]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_USER]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_USER]['DefaultWidth'] = "75"; +$fields[SYSLOG_WEBLOG_USER]['FieldAlign'] = "left"; +$fields[SYSLOG_WEBLOG_USER]['SearchField'] = SYSLOG_WEBLOG_USER; +$fields[SYSLOG_WEBLOG_METHOD]['FieldID'] = SYSLOG_WEBLOG_METHOD; +$fields[SYSLOG_WEBLOG_METHOD]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_METHOD'; +$fields[SYSLOG_WEBLOG_METHOD]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_METHOD]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_METHOD]['DefaultWidth'] = "50"; +$fields[SYSLOG_WEBLOG_METHOD]['FieldAlign'] = "center"; +$fields[SYSLOG_WEBLOG_METHOD]['SearchField'] = SYSLOG_WEBLOG_METHOD; +$fields[SYSLOG_WEBLOG_URL]['FieldID'] = SYSLOG_WEBLOG_URL; +$fields[SYSLOG_WEBLOG_URL]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_URL'; +$fields[SYSLOG_WEBLOG_URL]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_URL]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_URL]['DefaultWidth'] = "100%"; +$fields[SYSLOG_WEBLOG_URL]['FieldAlign'] = "left"; +$fields[SYSLOG_WEBLOG_URL]['SearchField'] = SYSLOG_WEBLOG_URL; +$fields[SYSLOG_WEBLOG_PVER]['FieldID'] = SYSLOG_WEBLOG_PVER; +$fields[SYSLOG_WEBLOG_PVER]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_PVER'; +$fields[SYSLOG_WEBLOG_PVER]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_PVER]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_PVER]['DefaultWidth'] = "50"; +$fields[SYSLOG_WEBLOG_PVER]['FieldAlign'] = "center"; +$fields[SYSLOG_WEBLOG_PVER]['SearchField'] = SYSLOG_WEBLOG_PVER; +$fields[SYSLOG_WEBLOG_STATUS]['FieldID'] = SYSLOG_WEBLOG_STATUS; +$fields[SYSLOG_WEBLOG_STATUS]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_STATUS'; +$fields[SYSLOG_WEBLOG_STATUS]['FieldType'] = FILTER_TYPE_NUMBER; +$fields[SYSLOG_WEBLOG_STATUS]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_STATUS]['DefaultWidth'] = "50"; +$fields[SYSLOG_WEBLOG_STATUS]['FieldAlign'] = "center"; +$fields[SYSLOG_WEBLOG_STATUS]['SearchField'] = SYSLOG_WEBLOG_STATUS; +$fields[SYSLOG_WEBLOG_BYTESSEND]['FieldID'] = SYSLOG_WEBLOG_BYTESSEND; +$fields[SYSLOG_WEBLOG_BYTESSEND]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_BYTESSEND'; +$fields[SYSLOG_WEBLOG_BYTESSEND]['FieldType'] = FILTER_TYPE_NUMBER; +$fields[SYSLOG_WEBLOG_BYTESSEND]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_BYTESSEND]['DefaultWidth'] = "75"; +$fields[SYSLOG_WEBLOG_BYTESSEND]['FieldAlign'] = "left"; +$fields[SYSLOG_WEBLOG_BYTESSEND]['SearchField'] = SYSLOG_WEBLOG_BYTESSEND; +$fields[SYSLOG_WEBLOG_REFERER]['FieldID'] = SYSLOG_WEBLOG_REFERER; +$fields[SYSLOG_WEBLOG_REFERER]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_REFERER'; +$fields[SYSLOG_WEBLOG_REFERER]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_REFERER]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_REFERER]['DefaultWidth'] = "100"; +$fields[SYSLOG_WEBLOG_REFERER]['FieldAlign'] = "left"; +$fields[SYSLOG_WEBLOG_REFERER]['SearchField'] = SYSLOG_WEBLOG_REFERER; +$fields[SYSLOG_WEBLOG_USERAGENT]['FieldID'] = SYSLOG_WEBLOG_USERAGENT; +$fields[SYSLOG_WEBLOG_USERAGENT]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_USERAGENT'; +$fields[SYSLOG_WEBLOG_USERAGENT]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_USERAGENT]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_USERAGENT]['DefaultWidth'] = "100"; +$fields[SYSLOG_WEBLOG_USERAGENT]['FieldAlign'] = "left"; +$fields[SYSLOG_WEBLOG_USERAGENT]['SearchField'] = SYSLOG_WEBLOG_USERAGENT; + // Message is the last element, this order is important for the Detail page for now! $fields[SYSLOG_MESSAGE]['FieldID'] = SYSLOG_MESSAGE; $fields[SYSLOG_MESSAGE]['FieldCaptionID'] = 'LN_FIELDS_MESSAGE'; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 7fac042..7834d92 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -203,6 +203,11 @@ function CreateLogLineTypesList( $selectedType ) $content['LOGLINETYPES']["winsyslog"]['type'] = "winsyslog"; $content['LOGLINETYPES']["winsyslog"]['DisplayName'] = "Adiscon WinSyslog"; if ( $selectedType == $content['LOGLINETYPES']["winsyslog"]['type'] ) { $content['LOGLINETYPES']["winsyslog"]['selected'] = "selected"; } else { $content['LOGLINETYPES']["winsyslog"]['selected'] = ""; } + + // Misc logline Types + $content['LOGLINETYPES']["misc"]['type'] = "misc"; + $content['LOGLINETYPES']["misc"]['DisplayName'] = "Miscellaneous logfiles"; + if ( $selectedType == $content['LOGLINETYPES']["misc"]['type'] ) { $content['LOGLINETYPES']["misc"]['selected'] = "selected"; } else { $content['LOGLINETYPES']["misc"]['selected'] = ""; } } function CreateSourceTypesList( $selectedSource ) diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 960f704..c95326a 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -171,6 +171,14 @@ $content['LN_FIELDS_MESSAGE'] = "Meldung"; $content['LN_FIELDS_EVENTSOURCE'] = "Event Source"; $content['LN_FIELDS_EVENTCATEGORY'] = "Event Category"; $content['LN_FIELDS_EVENTUSER'] = "Event User"; + $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; + $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; + $content['LN_FIELDS_WEBLOG_URL'] = "URL"; + $content['LN_FIELDS_WEBLOG_PVER'] = "Version"; + $content['LN_FIELDS_WEBLOG_STATUS'] = "Status"; + $content['LN_FIELDS_WEBLOG_BYTESSEND'] = "Bytes Send"; + $content['LN_FIELDS_WEBLOG_REFERER'] = "Referer"; + $content['LN_FIELDS_WEBLOG_USERAGENT'] = "User Agent"; // Install Page $content['LN_CFG_DBSERVER'] = "Datenbank Host"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 7fd5697..5c00294 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -171,6 +171,14 @@ $content['LN_FIELDS_EVENTLOGTYPE'] = "Eventlog Type"; $content['LN_FIELDS_EVENTSOURCE'] = "Event Source"; $content['LN_FIELDS_EVENTCATEGORY'] = "Event Category"; $content['LN_FIELDS_EVENTUSER'] = "Event User"; +$content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; +$content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; +$content['LN_FIELDS_WEBLOG_URL'] = "URL"; +$content['LN_FIELDS_WEBLOG_PVER'] = "Version"; +$content['LN_FIELDS_WEBLOG_STATUS'] = "Status"; +$content['LN_FIELDS_WEBLOG_BYTESSEND'] = "Bytes Send"; +$content['LN_FIELDS_WEBLOG_REFERER'] = "Referer"; +$content['LN_FIELDS_WEBLOG_USERAGENT'] = "User Agent"; // Install Page $content['LN_CFG_DBSERVER'] = "Database Host"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 68c2b55..fcb5dcd 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -175,6 +175,14 @@ $content['LN_FIELDS_EVENTLOGTYPE'] = "Tipo do Evento"; $content['LN_FIELDS_EVENTSOURCE'] = "Origem do Evento"; $content['LN_FIELDS_EVENTCATEGORY'] = "Categoria do Evento"; $content['LN_FIELDS_EVENTUSER'] = "Evento de Usu´rio"; + $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; + $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; + $content['LN_FIELDS_WEBLOG_URL'] = "URL"; + $content['LN_FIELDS_WEBLOG_PVER'] = "Version"; + $content['LN_FIELDS_WEBLOG_STATUS'] = "Status"; + $content['LN_FIELDS_WEBLOG_BYTESSEND'] = "Bytes Send"; + $content['LN_FIELDS_WEBLOG_REFERER'] = "Referer"; + $content['LN_FIELDS_WEBLOG_USERAGENT'] = "User Agent"; // Install Page $content['LN_CFG_DBSERVER'] = "Servidor BD"; From 3da89eac8fcf631efd6f88f5be28a3f1d2d288b4 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2008 11:13:29 +0200 Subject: [PATCH 114/142] Further adjustments for the new apache logparser --- src/include/constants_logstream.php | 12 ++++++++++-- src/include/functions_common.php | 8 ++++++++ src/lang/de/main.php | 1 + src/lang/en/main.php | 1 + src/lang/pt_BR/main.php | 1 + 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/include/constants_logstream.php b/src/include/constants_logstream.php index 202af10..81af7da 100644 --- a/src/include/constants_logstream.php +++ b/src/include/constants_logstream.php @@ -63,6 +63,7 @@ define('SYSLOG_EVENT_USER', 'user'); define('SYSLOG_WEBLOG_USER', 'http_user'); define('SYSLOG_WEBLOG_METHOD', 'http_method'); define('SYSLOG_WEBLOG_URL', 'http_url'); +define('SYSLOG_WEBLOG_QUERYSTRING', 'http_querystring'); define('SYSLOG_WEBLOG_PVER', 'http_ver'); define('SYSLOG_WEBLOG_STATUS', 'http_status'); define('SYSLOG_WEBLOG_BYTESSEND', 'http_bytessend'); @@ -202,9 +203,16 @@ $fields[SYSLOG_WEBLOG_URL]['FieldID'] = SYSLOG_WEBLOG_URL; $fields[SYSLOG_WEBLOG_URL]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_URL'; $fields[SYSLOG_WEBLOG_URL]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_WEBLOG_URL]['Sortable'] = false; -$fields[SYSLOG_WEBLOG_URL]['DefaultWidth'] = "100%"; +$fields[SYSLOG_WEBLOG_URL]['DefaultWidth'] = "200"; $fields[SYSLOG_WEBLOG_URL]['FieldAlign'] = "left"; $fields[SYSLOG_WEBLOG_URL]['SearchField'] = SYSLOG_WEBLOG_URL; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['FieldID'] = SYSLOG_WEBLOG_QUERYSTRING; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_QUERYSTRING'; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['FieldType'] = FILTER_TYPE_STRING; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['Sortable'] = false; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['DefaultWidth'] = "200"; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['FieldAlign'] = "left"; +$fields[SYSLOG_WEBLOG_QUERYSTRING]['SearchField'] = SYSLOG_WEBLOG_QUERYSTRING; $fields[SYSLOG_WEBLOG_PVER]['FieldID'] = SYSLOG_WEBLOG_PVER; $fields[SYSLOG_WEBLOG_PVER]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_PVER'; $fields[SYSLOG_WEBLOG_PVER]['FieldType'] = FILTER_TYPE_STRING; @@ -230,7 +238,7 @@ $fields[SYSLOG_WEBLOG_REFERER]['FieldID'] = SYSLOG_WEBLOG_REFERER; $fields[SYSLOG_WEBLOG_REFERER]['FieldCaptionID'] = 'LN_FIELDS_WEBLOG_REFERER'; $fields[SYSLOG_WEBLOG_REFERER]['FieldType'] = FILTER_TYPE_STRING; $fields[SYSLOG_WEBLOG_REFERER]['Sortable'] = false; -$fields[SYSLOG_WEBLOG_REFERER]['DefaultWidth'] = "100"; +$fields[SYSLOG_WEBLOG_REFERER]['DefaultWidth'] = "200"; $fields[SYSLOG_WEBLOG_REFERER]['FieldAlign'] = "left"; $fields[SYSLOG_WEBLOG_REFERER]['SearchField'] = SYSLOG_WEBLOG_REFERER; $fields[SYSLOG_WEBLOG_USERAGENT]['FieldID'] = SYSLOG_WEBLOG_USERAGENT; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 7834d92..94ad1d3 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -942,6 +942,14 @@ function GetEventTime($szTimStr) $eventtime[EVTIME_TIMEZONE] = date_default_timezone_get(); // WTF TODO! $eventtime[EVTIME_MICROSECONDS] = 0; } + // Sample: 16/Sep/2008:13:37:47 +0200 + else if ( preg_match("/([0-9]{1,2})\/(...)\/([0-9]{1,4}):([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}) \+([0-9]{1,4})/", $szTimStr, $out ) ) + { + // Apache Logfile typical timestamp + $eventtime[EVTIME_TIMESTAMP] = mktime($out[4], $out[5], $out[6], GetMonthFromString($out[2]), $out[1], $out[3]); + $eventtime[EVTIME_TIMEZONE] = date_default_timezone_get(); // > WTF TODO! > $out[7] + $eventtime[EVTIME_MICROSECONDS] = 0; + } else { $eventtime[EVTIME_TIMESTAMP] = 0; diff --git a/src/lang/de/main.php b/src/lang/de/main.php index c95326a..7efb6eb 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -174,6 +174,7 @@ $content['LN_FIELDS_MESSAGE'] = "Meldung"; $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; $content['LN_FIELDS_WEBLOG_URL'] = "URL"; + $content['LN_FIELDS_WEBLOG_QUERYSTRING'] = "Querystring"; $content['LN_FIELDS_WEBLOG_PVER'] = "Version"; $content['LN_FIELDS_WEBLOG_STATUS'] = "Status"; $content['LN_FIELDS_WEBLOG_BYTESSEND'] = "Bytes Send"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 5c00294..3481253 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -174,6 +174,7 @@ $content['LN_FIELDS_EVENTUSER'] = "Event User"; $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; $content['LN_FIELDS_WEBLOG_URL'] = "URL"; +$content['LN_FIELDS_WEBLOG_QUERYSTRING'] = "Querystring"; $content['LN_FIELDS_WEBLOG_PVER'] = "Version"; $content['LN_FIELDS_WEBLOG_STATUS'] = "Status"; $content['LN_FIELDS_WEBLOG_BYTESSEND'] = "Bytes Send"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index fcb5dcd..6a584c2 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -178,6 +178,7 @@ $content['LN_FIELDS_EVENTUSER'] = "Evento de Usu´rio"; $content['LN_FIELDS_WEBLOG_USER'] = "Remote User"; $content['LN_FIELDS_WEBLOG_METHOD'] = "Method"; $content['LN_FIELDS_WEBLOG_URL'] = "URL"; + $content['LN_FIELDS_WEBLOG_QUERYSTRING'] = "Querystring"; $content['LN_FIELDS_WEBLOG_PVER'] = "Version"; $content['LN_FIELDS_WEBLOG_STATUS'] = "Status"; $content['LN_FIELDS_WEBLOG_BYTESSEND'] = "Bytes Send"; From f016ae6f34dba90025b369b6b4fb15fc8a175cb1 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 17 Sep 2008 11:29:24 +0200 Subject: [PATCH 115/142] begin adding doc (seed from rsyslog) this commit is more or less a test for my environment setup sorry for the disturbance --- doc/manual.html | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 doc/manual.html diff --git a/doc/manual.html b/doc/manual.html new file mode 100644 index 0000000..91e7d0e --- /dev/null +++ b/doc/manual.html @@ -0,0 +1,100 @@ + +rsyslog documentation + +

    RSyslog - Documentation

    +

    Rsyslog +is an enhanced syslogd +supporting, among others, MySQL, +PostgreSQL, failover +log destinations, syslog/tcp, fine grain output format +control, high precision timestamps, queued operations and the ability to filter on any message +part. +It is quite compatible to stock sysklogd and can be used as a drop-in +replacement. Its +advanced features make it suitable for enterprise-class, encryption protected syslog +relay chains while at the same time being very easy to setup for the +novice user. And as we know what enterprise users really need, there is +also professional +rsyslog support available directly from the source!

    +

    This documentation is for version 3.21.5 (devel branch) of rsyslog. +Visit the rsyslog status page to obtain current +version information and project status. +

    If you like rsyslog, you might +want to lend us a helping hand. It doesn't require a lot of +time - even a single mouse click helps. Learn how to help the rsyslog project. +Due to popular demand, there is now a side-by-side comparison +between rsyslog and syslog-ng.

    +

    If you are upgrading from rsyslog v2 or stock sysklogd, +be +sure to read the rsyslog v3 compatibility document! It will work even +if you do not read the doc, but doing so will definitely improve your experience.

    +

    Follow +the links below for the

    +

    We have some in-depth papers on

    + +

    Our rsyslog history +page is for you if you would like to learn a little more +on why there is an rsyslog at all. If you are interested why you should +care about rsyslog at all, you may want to read Rainer's essay on "why +the world needs another syslogd".

    +

    Documentation is added continuously. Please note that the +documentation here +matches only the current version of rsyslog. If you use an older +version, be sure to use the doc that came with it.

    +

    You can also browse the following online resources:

    + +

    And don't forget about the rsyslog +mailing list. If you are interested in the "backstage", you +may find +Rainer's +blog an +interesting read (filter on syslog and rsyslog tags). +If you would like to use rsyslog source code inside your open source project, you can do that without +any restriction as long as your license is GPLv3 compatible. If your license is incompatible to GPLv3, +you may even be still permitted to use rsyslog source code. However, then you need to look at the way +rsyslog is licensed.

    +

    Feedback is always welcome, but if you have a support question, please do not +mail Rainer directly (why not?). + From 4c03d1c53a8ba3ea1b084be6f722ec116dc16d75 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 11:52:29 +0200 Subject: [PATCH 116/142] Fixed bug in apache2 msgparser --- src/classes/logstreamdisk.class.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index c9a721b..b4af26f 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -233,18 +233,18 @@ class LogStreamDisk extends LogStream { else $ret = $this->ReadNextBackwards($uID, $arrProperitesOut); - // Only PARSE on success! - if ( $ret == SUCCESS && $bParseMessage) - { - // Line Parser Hook here - $this->_logStreamConfigObj->_lineParser->ParseLine($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); - - // Run optional Message Parsers now - $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + // Only PARSE on success! + if ( $ret == SUCCESS && $bParseMessage) + { + // Line Parser Hook here + $this->_logStreamConfigObj->_lineParser->ParseLine($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); + + // Run optional Message Parsers now + $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); - // Set uID to the PropertiesOut! - $arrProperitesOut[SYSLOG_UID] = $uID; - } + // Set uID to the PropertiesOut! + $arrProperitesOut[SYSLOG_UID] = $uID; + } // Loop until the filter applies, or another error occurs. } while ( $this->ApplyFilters($ret, $arrProperitesOut) != SUCCESS && $ret == SUCCESS ); @@ -855,7 +855,7 @@ class LogStreamDisk extends LogStream { if ( !$bEval ) { - // unmatching filter, rest property array + // unmatching filter, reset property array foreach ( $this->_arrProperties as $property ) $arrProperitesOut[$property] = ''; From 5816c0f4d9aa369bce813834478dd15b8bfab4d3 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 17 Sep 2008 14:36:12 +0200 Subject: [PATCH 117/142] Changed phpLogCon Help Page link --- src/templates/include_menu.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/include_menu.html b/src/templates/include_menu.html index 409b5bb..59486f5 100644 --- a/src/templates/include_menu.html +++ b/src/templates/include_menu.html @@ -8,7 +8,7 @@ -

    {LN_MENU_HELP}{LN_MENU_HELP} {LN_MENU_SEARCHINKB} {LN_MENU_HELP}{LN_MENU_HELP} {LN_MENU_SEARCHINKB}
    {LN_GEN_STRCHARLIMIT}
    {LN_GEN_ENTRIESPERPAGE}
    {LN_GEN_MSGCHARLIMIT}
    {LN_GEN_STRCHARLIMIT}
    {LN_GEN_ENTRIESPERPAGE}
    {LN_GEN_AUTORELOADSECONDS}
    {LN_GEN_POPUPMENUTIMEOUT}
    {LN_GEN_CUSTBTNCAPT} {LN_ADMIN_GLOBALONLY}
    {LN_ADMIN_SCRIPTTIMEOUT}
    {LN_GEN_DEBUGUSERLOGIN}
    - - + - - - diff --git a/src/templates/include_header.html b/src/templates/include_header.html index ca1cab5..6c9ea48 100644 --- a/src/templates/include_header.html +++ b/src/templates/include_header.html @@ -6,6 +6,9 @@ {EXTRA_STYLESHEET} + {EXTRA_JAVASCRIPT} From 52990a07d3b073a9143964fa5d69ece982ba9932 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 14:29:17 +0200 Subject: [PATCH 128/142] Changed IUT Type to WEBSERVER Logfile in general, as IIS is supported as well now --- src/classes/logstreamconfig.class.php | 6 ++++++ src/classes/msgparsers/msgparser.apache2.class.php | 2 +- src/classes/msgparsers/msgparser.iis.class.php | 2 +- src/include/constants_errors.php | 1 + src/include/constants_filters.php | 2 +- src/include/constants_general.php | 4 ++-- src/include/functions_common.php | 2 +- 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/classes/logstreamconfig.class.php b/src/classes/logstreamconfig.class.php index dafc055..e8cf61c 100644 --- a/src/classes/logstreamconfig.class.php +++ b/src/classes/logstreamconfig.class.php @@ -131,6 +131,12 @@ abstract class LogStreamConfig { public function ProcessMsgParsers($szMsg, &$arrArguments) { + // Abort msgparsers if we have less then 5 seconds of processing time! + global $content, $gl_starttime; + $scriptruntime = intval(microtime_float() - $gl_starttime); + if ( $scriptruntime > ($content['MaxExecutionTime']-5) ) + return ERROR_MSG_SCANABORTED; + // Process if set! if ( $this->_msgParserObjList != null ) { diff --git a/src/classes/msgparsers/msgparser.apache2.class.php b/src/classes/msgparsers/msgparser.apache2.class.php index 11cc3b7..7013a8e 100644 --- a/src/classes/msgparsers/msgparser.apache2.class.php +++ b/src/classes/msgparsers/msgparser.apache2.class.php @@ -136,7 +136,7 @@ class MsgParser_apache2 extends MsgParser { } // Set IUT Property if success! - $arrArguments[SYSLOG_MESSAGETYPE] = IUT_APACHELOG; + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_WEBSERVERLOG; // If we reached this position, return success! return SUCCESS; diff --git a/src/classes/msgparsers/msgparser.iis.class.php b/src/classes/msgparsers/msgparser.iis.class.php index d15eb33..9f5d45f 100644 --- a/src/classes/msgparsers/msgparser.iis.class.php +++ b/src/classes/msgparsers/msgparser.iis.class.php @@ -145,7 +145,7 @@ class MsgParser_iis extends MsgParser { } // Set IUT Property if success! - $arrArguments[SYSLOG_MESSAGETYPE] = IUT_APACHELOG; + $arrArguments[SYSLOG_MESSAGETYPE] = IUT_WEBSERVERLOG; // If we reached this position, return success! return SUCCESS; diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index 7d26187..b47aba6 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -63,5 +63,6 @@ define('ERROR_DB_DBFIELDNOTFOUND', 19); define('ERROR_MSG_NOMATCH', 18); define('ERROR_CHARTS_NOTCONFIGURED', 20); define('ERROR_MSG_SKIPMESSAGE', 21); +define('ERROR_MSG_SCANABORTED', 22); ?> diff --git a/src/include/constants_filters.php b/src/include/constants_filters.php index 459951d..1e35741 100644 --- a/src/include/constants_filters.php +++ b/src/include/constants_filters.php @@ -111,6 +111,6 @@ $content['filter_severity_list'][] = array( "ID" => SYSLOG_DEBUG, "DisplayName" $content['filter_messagetype_list'][] = array( "ID" => IUT_Syslog, "DisplayName" => "Syslog", "selected" => "" ); $content['filter_messagetype_list'][] = array( "ID" => IUT_NT_EventReport, "DisplayName" => "WinEventLog", "selected" => "" ); $content['filter_messagetype_list'][] = array( "ID" => IUT_File_Monitor, "DisplayName" => "File Monitor", "selected" => "" ); -$content['filter_messagetype_list'][] = array( "ID" => IUT_APACHELOG, "DisplayName" => "Apache Webserver Logfile", "selected" => "" ); +$content['filter_messagetype_list'][] = array( "ID" => IUT_WEBSERVERLOG, "DisplayName" => "Webserver Logfile", "selected" => "" ); ?> \ No newline at end of file diff --git a/src/include/constants_general.php b/src/include/constants_general.php index c0eb746..821339c 100644 --- a/src/include/constants_general.php +++ b/src/include/constants_general.php @@ -178,7 +178,7 @@ define('IUT_IMAPProbe', '21'); define('IUT_NNTPProbe', '22'); define('IUT_WEVTMONV2', '23'); define('IUT_SMTPLISTENER', '24'); -define('IUT_APACHELOG', '10001'); +define('IUT_WEBSERVERLOG', '10001'); $msgtype_colors[IUT_Unknown] = "#D0FBDC"; $msgtype_colors[IUT_Syslog] = "#D0FBF1"; $msgtype_colors[IUT_Heartbeat] = "#D0EEFB"; @@ -201,7 +201,7 @@ $msgtype_colors[IUT_IMAPProbe] = "#D0FBE8"; $msgtype_colors[IUT_NNTPProbe] = "#D0F7FB"; $msgtype_colors[IUT_WEVTMONV2] = "#CCE4D2"; $msgtype_colors[IUT_SMTPLISTENER] = "#CCE4DE"; -$msgtype_colors[IUT_APACHELOG] = "#E1FBD0"; +$msgtype_colors[IUT_WEBSERVERLOG] = "#E1FBD0"; // --- ?> \ No newline at end of file diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 539eb10..3bb90c4 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1113,7 +1113,7 @@ function ReverseResolveIP( $szIP, $prepend, $append ) { global $gl_starttime, $content; - // Substract 5 savety seconds! + // Substract 5 seconds we need to finish processing! $scriptruntime = intval(microtime_float() - $gl_starttime); if ( $scriptruntime > ($content['MaxExecutionTime']-5) ) return ""; From 36f6d93074be3fb825b3fbb7bde1ef23536a93e3 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 15:21:33 +0200 Subject: [PATCH 129/142] Added php script timeout handling into disk logstream class This means the disk logstream will stop reading messages if we are to close before the php script timeout. --- src/classes/logstreamdisk.class.php | 15 ++++++++++++++- src/include/constants_errors.php | 3 ++- src/lang/de/main.php | 8 +++++++- src/lang/en/main.php | 4 ++-- src/lang/pt_BR/main.php | 8 +++++++- src/templates/index.html | 13 ++++++++++++- 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index d9d778f..9ab00f4 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -225,6 +225,8 @@ class LogStreamDisk extends LogStream { */ public function ReadNext(&$uID, &$arrProperitesOut, $bParseMessage = true) { + global $content, $gl_starttime; + do { // Read next entry first! @@ -250,7 +252,18 @@ class LogStreamDisk extends LogStream { $arrProperitesOut[SYSLOG_UID] = $uID; } - // Loop until the filter applies, or another error occurs. + // Check how long we are running. If only two seconds of execution time are left, we abort further reading! + $scriptruntime = intval(microtime_float() - $gl_starttime); + if ( $scriptruntime > ($content['MaxExecutionTime']-2) ) + { + // This may display a warning message, so the user knows we stopped reading records because of the script timeout. + $content['logstream_warning'] = "false"; + $content['logstream_warning_details'] = $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT']; + + return ERROR_FILE_NOMORETIME; + } + + // Loop until the filter applies, or another error occurs, and we still have TIME! } while ( $this->ApplyFilters($ret, $arrProperitesOut) != SUCCESS && $ret == SUCCESS ); // reached here means return result! diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index b47aba6..7952356 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -46,6 +46,7 @@ define('ERROR_FILE_CANT_CLOSE', 3); define('ERROR_FILE_EOF', 4); define('ERROR_FILE_BOF', 5); define('ERROR_FILE_NOT_READABLE', 15); +define('ERROR_FILE_NOMORETIME', 22); define('ERROR_UNDEFINED', 6); define('ERROR_EOS', 7); define('ERROR_NOMORERECORDS', 8); @@ -63,6 +64,6 @@ define('ERROR_DB_DBFIELDNOTFOUND', 19); define('ERROR_MSG_NOMATCH', 18); define('ERROR_CHARTS_NOTCONFIGURED', 20); define('ERROR_MSG_SKIPMESSAGE', 21); -define('ERROR_MSG_SCANABORTED', 22); +define('ERROR_MSG_SCANABORTED', 23); ?> diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 6ea1ee3..74260ca 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -88,7 +88,13 @@ $content['LN_ERROR_NORECORDS'] = "Es wurden keine syslog-Einträge gefunden. $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; $content['LN_GEN_MOREINFORMATION'] = "More Information"; - + $content['LN_FOOTER_PAGERENDERED'] = "Page rendered in"; + $content['LN_FOOTER_DBQUERIES'] = "DB queries"; + $content['LN_FOOTER_GZIPENABLED'] = "GZIP enabled"; + $content['LN_FOOTER_SCRIPTTIMEOUT'] = "Script Timeout"; + $content['LN_FOOTER_SECONDS'] = "seconds"; + $content['LN_WARNING_LOGSTREAMTITLE'] = "Logstream Warning"; + $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT'] = "While reading the logstream, the php script timeout forced me to abort at this point.

    If you want to avoid this, please increase the phpLogCon script timeout in your config.php. If the user system is installed, you can do that in Admin center."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Suchen"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 7de84fb..620585f 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -89,13 +89,13 @@ $content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least o $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; $content['LN_GEN_MOREINFORMATION'] = "More Information"; - $content['LN_FOOTER_PAGERENDERED'] = "Page rendered in"; $content['LN_FOOTER_DBQUERIES'] = "DB queries"; $content['LN_FOOTER_GZIPENABLED'] = "GZIP enabled"; $content['LN_FOOTER_SCRIPTTIMEOUT'] = "Script Timeout"; $content['LN_FOOTER_SECONDS'] = "seconds"; - + $content['LN_WARNING_LOGSTREAMTITLE'] = "Logstream Warning"; + $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT'] = "While reading the logstream, the php script timeout forced me to abort at this point.

    If you want to avoid this, please increase the phpLogCon script timeout in your config.php. If the user system is installed, you can do that in Admin center."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index ef87c45..4d6d853 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -92,7 +92,13 @@ $content['LN_ERROR_NORECORDS'] = "Sem mensagens encontradas."; $content['LN_GEN_ERROR_INVALIDEXPORTTYPE'] = "Invalid Export format selected, or other parameters were wrong."; $content['LN_GEN_ERROR_SOURCENOTFOUND'] = "The Source with ID '%1' could not be found."; $content['LN_GEN_MOREINFORMATION'] = "More Information"; - + $content['LN_FOOTER_PAGERENDERED'] = "Page rendered in"; + $content['LN_FOOTER_DBQUERIES'] = "DB queries"; + $content['LN_FOOTER_GZIPENABLED'] = "GZIP enabled"; + $content['LN_FOOTER_SCRIPTTIMEOUT'] = "Script Timeout"; + $content['LN_FOOTER_SECONDS'] = "seconds"; + $content['LN_WARNING_LOGSTREAMTITLE'] = "Logstream Warning"; + $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT'] = "While reading the logstream, the php script timeout forced me to abort at this point.

    If you want to avoid this, please increase the phpLogCon script timeout in your config.php. If the user system is installed, you can do that in Admin center."; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/index.html b/src/templates/index.html index 36f12d1..b2bd465 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -364,7 +364,6 @@
    Made by Adiscon GmbH (2008) + Made by Adiscon GmbH (2008)  phpLogCon Version {BUILDNUMBER} +  Partners: +  Rsyslog |  WinSyslog - Page rendered: {PAGERENDERTIME} seconds -  | DB queries: {TOTALQUERIES} -  | GZIP enabled: {GzipCompressionEnmabled} + + + {LN_FOOTER_PAGERENDERED}: {PAGERENDERTIME} {LN_FOOTER_SECONDS} +  | {LN_FOOTER_DBQUERIES}: {TOTALQUERIES} +  | {LN_FOOTER_GZIPENABLED}: {GzipCompressionEnmabled} +  | {LN_FOOTER_SCRIPTTIMEOUT}: {MaxExecutionTime} {LN_FOOTER_SECONDS} +
    -

    @@ -377,5 +376,17 @@

    + +

    +
    +
    +
    {LN_WARNING_LOGSTREAMTITLE}
    +

    {logstream_warning_details}

    +
    +

    +
    +

    + + \ No newline at end of file From 74cd4a5be72e035cc06be0fbb8feec7b69cd2acb Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 16:14:32 +0200 Subject: [PATCH 130/142] Added support to display warning messages in the index page, and also added more information links --- src/classes/logstreamdisk.class.php | 4 +++- src/include/constants_errors.php | 1 + src/include/functions_common.php | 4 ++++ src/index.php | 3 +++ src/lang/en/main.php | 1 + src/templates/index.html | 14 +++++++++++++- 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 9ab00f4..3eefe23 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -259,7 +259,9 @@ class LogStreamDisk extends LogStream { // This may display a warning message, so the user knows we stopped reading records because of the script timeout. $content['logstream_warning'] = "false"; $content['logstream_warning_details'] = $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT']; - + $content['logstream_warning_code'] = ERROR_FILE_NOMORETIME; + + // Return error code return ERROR_FILE_NOMORETIME; } diff --git a/src/include/constants_errors.php b/src/include/constants_errors.php index 7952356..b36cd5d 100644 --- a/src/include/constants_errors.php +++ b/src/include/constants_errors.php @@ -51,6 +51,7 @@ define('ERROR_UNDEFINED', 6); define('ERROR_EOS', 7); define('ERROR_NOMORERECORDS', 8); define('ERROR_FILTER_NOT_MATCH', 9); +define('ERROR_SOURCENOTFOUND', 24); define('ERROR_DB_CONNECTFAILED', 10); define('ERROR_DB_CANNOTSELECTDB', 11); diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 3bb90c4..add4c3b 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1341,6 +1341,10 @@ function GetErrorMessage($errorCode) case ERROR_CHARTS_NOTCONFIGURED: return $content['LN_ERROR_CHARTS_NOTCONFIGURED']; + case ERROR_FILE_NOMORETIME: + return $content['LN_ERROR_FILE_NOMORETIME']; + case ERROR_SOURCENOTFOUND: + return $content['LN_GEN_ERROR_SOURCENOTFOUND']; default: return GetAndReplaceLangStr( $content['LN_ERROR_UNKNOWN'], $errorCode ); diff --git a/src/index.php b/src/index.php index 211fe63..546c4b4 100644 --- a/src/index.php +++ b/src/index.php @@ -268,6 +268,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // This will disable to Main SyslogView and show an error message $content['syslogmessagesenabled'] = "false"; $content['detailederror'] = $content['LN_ERROR_NORECORDS']; + $content['detailederror_code'] = ERROR_NOMORERECORDS; } // --- @@ -934,6 +935,7 @@ if ( isset($content['Sources'][$currentSourceID]) ) // This will disable to Main SyslogView and show an error message $content['syslogmessagesenabled'] = "false"; $content['detailederror'] = GetErrorMessage($res); + $content['detailederror_code'] = $res; if ( isset($extraErrorDescription) ) $content['detailederror'] .= "

    " . GetAndReplaceLangStr( $content['LN_SOURCES_ERROR_EXTRAMSG'], $extraErrorDescription); @@ -946,6 +948,7 @@ else { $content['syslogmessagesenabled'] = "false"; $content['detailederror'] = GetAndReplaceLangStr( $content['LN_GEN_ERROR_SOURCENOTFOUND'], $currentSourceID); + $content['detailederror_code'] = ERROR_SOURCENOTFOUND; } // --- diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 620585f..afc7f14 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -96,6 +96,7 @@ $content['LN_ERROR_DB_DBFIELDNOTFOUND'] = "Database Field mapping for at least o $content['LN_FOOTER_SECONDS'] = "seconds"; $content['LN_WARNING_LOGSTREAMTITLE'] = "Logstream Warning"; $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT'] = "While reading the logstream, the php script timeout forced me to abort at this point.

    If you want to avoid this, please increase the phpLogCon script timeout in your config.php. If the user system is installed, you can do that in Admin center."; + $content['LN_ERROR_FILE_NOMORETIME'] = "No more time for processing left"; // Topmenu Entries $content['LN_MENU_SEARCH'] = "Search"; diff --git a/src/templates/index.html b/src/templates/index.html index b2bd465..16c6b41 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -369,7 +369,13 @@
    {LN_ERROR_NORECORDS} - {LN_GEN_ERRORDETAILS}
    -

    {detailederror}

    +

    {detailederror}

    +

    + + + {LN_GEN_MOREINFORMATION} + +



    @@ -382,6 +388,12 @@
    {LN_WARNING_LOGSTREAMTITLE}

    {logstream_warning_details}

    +

    + + + {LN_GEN_MOREINFORMATION} + +



    From 976bc44cb020fca94aa022fbe9a68e0b47cb273e Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 16:30:48 +0200 Subject: [PATCH 131/142] Added the option "ViewStringCharacterLimit" into the front end installation. --- src/install.php | 12 ++++++++++++ src/lang/de/main.php | 1 + src/lang/en/main.php | 1 + src/lang/pt_BR/main.php | 1 + src/templates/install.html | 4 ++++ 5 files changed, 19 insertions(+) diff --git a/src/install.php b/src/install.php index f2e70bc..e586776 100644 --- a/src/install.php +++ b/src/install.php @@ -225,6 +225,7 @@ else if ( $content['INSTALL_STEP'] == 3 ) // --- Read and predefine Frontend options if ( isset($_SESSION['ViewMessageCharacterLimit']) ) { $content['ViewMessageCharacterLimit'] = $_SESSION['ViewMessageCharacterLimit']; } else { $content['ViewMessageCharacterLimit'] = 80; } + if ( isset($_SESSION['ViewStringCharacterLimit']) ) { $content['ViewStringCharacterLimit'] = $_SESSION['ViewStringCharacterLimit']; } else { $content['ViewStringCharacterLimit'] = 30; } if ( isset($_SESSION['ViewEntriesPerPage']) ) { $content['ViewEntriesPerPage'] = $_SESSION['ViewEntriesPerPage']; } else { $content['ViewEntriesPerPage'] = 50; } if ( isset($_SESSION['ViewEnableDetailPopups']) ) { $content['ViewEnableDetailPopups'] = $_SESSION['ViewEnableDetailPopups']; } else { $content['ViewEnableDetailPopups'] = 1; } if ( $content['ViewEnableDetailPopups'] == 1 ) @@ -328,6 +329,15 @@ else if ( $content['INSTALL_STEP'] == 4 ) else $_SESSION['ViewMessageCharacterLimit'] = 80; // Fallback default! + if ( isset($_POST['ViewStringCharacterLimit']) ) + { + $_SESSION['ViewStringCharacterLimit'] = intval( DB_RemoveBadChars($_POST['ViewStringCharacterLimit']) ); + if ( $_SESSION['ViewStringCharacterLimit'] < 0 ) + $_SESSION['ViewStringCharacterLimit'] = 30; // Fallback default! + } + else + $_SESSION['ViewStringCharacterLimit'] = 30; // Fallback default! + if ( isset($_POST['ViewEntriesPerPage']) ) { $_SESSION['ViewEntriesPerPage'] = intval( DB_RemoveBadChars($_POST['ViewEntriesPerPage']) ); @@ -644,6 +654,7 @@ else if ( $content['INSTALL_STEP'] == 8 ) // Start replacing existing sample configurations $patterns[] = "/\\\$CFG\['ViewMessageCharacterLimit'\] = [0-9]{1,2};/"; + $patterns[] = "/\\\$CFG\['ViewStringCharacterLimit'\] = [0-9]{1,2};/"; $patterns[] = "/\\\$CFG\['ViewEntriesPerPage'\] = [0-9]{1,2};/"; $patterns[] = "/\\\$CFG\['ViewEnableDetailPopups'\] = [0-9]{1,2};/"; $patterns[] = "/\\\$CFG\['EnableIPAddressResolve'\] = [0-9]{1,2};/"; @@ -657,6 +668,7 @@ else if ( $content['INSTALL_STEP'] == 8 ) $patterns[] = "/\\\$CFG\['UserDBLoginRequired'\] = (.*?);/"; $replacements[] = "\$CFG['ViewMessageCharacterLimit'] = " . $_SESSION['ViewMessageCharacterLimit'] . ";"; + $replacements[] = "\$CFG['ViewStringCharacterLimit'] = " . $_SESSION['ViewStringCharacterLimit'] . ";"; $replacements[] = "\$CFG['ViewEntriesPerPage'] = " . $_SESSION['ViewEntriesPerPage'] . ";"; $replacements[] = "\$CFG['ViewEnableDetailPopups'] = " . $_SESSION['ViewEnableDetailPopups'] . ";"; $replacements[] = "\$CFG['EnableIPAddressResolve'] = " . $_SESSION['EnableIPAddressResolve'] . ";"; diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 74260ca..2010067 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -261,6 +261,7 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_INSTALL_FRONTEND'] = "Frontend Options"; $content['LN_INSTALL_NUMOFSYSLOGS'] = "Number of syslog messages per page"; $content['LN_INSTALL_MSGCHARLIMIT'] = "Message character limit for the main view"; + $content['LN_INSTALL_STRCHARLIMIT'] = "Character display limit for all string type fields"; $content['LN_INSTALL_SHOWDETAILPOP'] = "Show message details popup"; $content['LN_INSTALL_AUTORESOLVIP'] = "Automatically resolved IP Addresses (inline)"; $content['LN_INSTALL_USERDBOPTIONS'] = "User Database Options"; diff --git a/src/lang/en/main.php b/src/lang/en/main.php index afc7f14..fefbf44 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -263,6 +263,7 @@ $content['LN_INSTALL_PROGRESS'] = "Install Progress: "; $content['LN_INSTALL_FRONTEND'] = "Frontend Options"; $content['LN_INSTALL_NUMOFSYSLOGS'] = "Number of syslog messages per page"; $content['LN_INSTALL_MSGCHARLIMIT'] = "Message character limit for the main view"; +$content['LN_INSTALL_STRCHARLIMIT'] = "Character display limit for all string type fields"; $content['LN_INSTALL_SHOWDETAILPOP'] = "Show message details popup"; $content['LN_INSTALL_AUTORESOLVIP'] = "Automatically resolved IP Addresses (inline)"; $content['LN_INSTALL_USERDBOPTIONS'] = "User Database Options"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index 4d6d853..f1dcd69 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -265,6 +265,7 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_INSTALL_FRONTEND'] = "Frontend Options"; $content['LN_INSTALL_NUMOFSYSLOGS'] = "Number of syslog messages per page"; $content['LN_INSTALL_MSGCHARLIMIT'] = "Message character limit for the main view"; + $content['LN_INSTALL_STRCHARLIMIT'] = "Character display limit for all string type fields"; $content['LN_INSTALL_SHOWDETAILPOP'] = "Show message details popup"; $content['LN_INSTALL_AUTORESOLVIP'] = "Automatically resolved IP Addresses (inline)"; $content['LN_INSTALL_USERDBOPTIONS'] = "User Database Options"; diff --git a/src/templates/install.html b/src/templates/install.html index 61f7641..7e9c130 100644 --- a/src/templates/install.html +++ b/src/templates/install.html @@ -142,6 +142,10 @@
    {LN_INSTALL_MSGCHARLIMIT}
    {LN_INSTALL_STRCHARLIMIT}
    {LN_INSTALL_SHOWDETAILPOP} From c85667a00e81b8ae2619277c3209040ca6cc1073 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Thu, 18 Sep 2008 18:00:52 +0200 Subject: [PATCH 132/142] Added oracle page which helps building usefull search links used within phpLogCon --- src/asktheoracle.php | 152 +++++++++++++++++++++++++++++++ src/include/functions_common.php | 16 +++- src/lang/en/main.php | 8 ++ src/statistics.php | 1 + src/templates/asktheoracle.html | 74 +++++++++++++++ src/templates/statistics.html | 8 +- 6 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 src/asktheoracle.php create mode 100644 src/templates/asktheoracle.html diff --git a/src/asktheoracle.php b/src/asktheoracle.php new file mode 100644 index 0000000..296da6f --- /dev/null +++ b/src/asktheoracle.php @@ -0,0 +1,152 @@ + This "oracle" is a helper page which generates and shows a bunch + * of usefull links ;)! + * + * All directives are explained within this file + * + * Copyright (C) 2008 Adiscon GmbH. + * + * This file is part of phpLogCon. + * + * PhpLogCon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PhpLogCon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with phpLogCon. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this + * distribution + ********************************************************************* +*/ + +// *** Default includes and procedures *** // +define('IN_PHPLOGCON', true); +$gl_root_path = './'; + +// Now include necessary include files! +include($gl_root_path . 'include/functions_common.php'); +include($gl_root_path . 'include/functions_frontendhelpers.php'); +include($gl_root_path . 'include/functions_filters.php'); + +InitPhpLogCon(); +InitSourceConfigs(); +InitFrontEndDefaults(); // Only in WebFrontEnd +InitFilterHelpers(); // Helpers for frontend filtering! +// --- + +// --- Define Extra Stylesheet! +//$content['EXTRA_STYLESHEET'] = '' . "\r\n"; +//$content['EXTRA_STYLESHEET'] .= ''; +// --- + +// --- READ Vars +if ( isset($_GET['type']) ) + $content['oracle_type'] = $_GET['type']; +else + $content['oracle_type'] = ""; + +if ( isset($_GET['query']) ) + $content['oracle_query'] = $_GET['query']; +else + $content['oracle_query'] = ""; + +if ( isset($_GET['uid']) ) + $content['uid_current'] = $_GET['uid']; +else + $content['uid_current'] = "-1"; + +// Init + +// --- BEGIN Custom Code + +// Set readable type +if ( $content['oracle_type'] == "ip" ) + $content['oracle_type_readable'] = "ip"; +else if ( $content['oracle_type'] == "domain" ) + $content['oracle_type_readable'] = "domain"; +else + $content['oracle_type_readable'] = "unknown type"; + +$content['ORACLE_HELP_DETAIL'] = GetAndReplaceLangStr( $content['LN_ORACLE_HELP_DETAIL'], $content['oracle_type_readable'], $content['oracle_query'] ) ; + + +// Enable help links! +$content['helplinksenabled'] = true; + +// Loop through all Sources +$i = 0; +foreach( $content['Sources'] as $mySource ) +{ + $myHelpLink['SourceName'] = GetAndReplaceLangStr("Source %1",$mySource['Name'] ); + $myHelpLink['MsgUrl'] = $content['BASEPATH'] . "index.php?filter=" . urlencode($content['oracle_query']) . "&search=Search&sourceid=" . $mySource['ID']; + $myHelpLink['MsgDisplayName'] = GetAndReplaceLangStr( $content['LN_ORACLE_SEARCHINFIELD'], "Message" ); + $myHelpLink['SourceUrl'] = $content['BASEPATH'] . "index.php?filter=" . urlencode("source:=" . $content['oracle_query']) . "&search=Search&sourceid=" . $mySource['ID']; + $myHelpLink['SourceDisplayName'] = GetAndReplaceLangStr( $content['LN_ORACLE_SEARCHINFIELD'], "Source" ); + + // --- Set CSS Class + if ( $i % 2 == 0 ) + $myHelpLink['cssclass'] = "line1"; + else + $myHelpLink['cssclass'] = "line2"; + $i++; + // --- + + // Add to help Link array! + $content['HelpLinks'][] = $myHelpLink; +} + +/* +if ( isset($content['Sources'][$currentSourceID]) ) // && $content['uid_current'] != UID_UNKNOWN ) // && $content['Sources'][$currentSourceID]['SourceType'] == SOURCE_DISK ) +{ + // Obtain and get the Config Object + $stream_config = $content['Sources'][$currentSourceID]['ObjRef']; + + // Create LogStream Object + $stream = $stream_config->LogStreamFactory($stream_config); +// $stream->SetFilter($content['searchstr']); + + // --- Init the fields we need + foreach($fields as $mycolkey => $myfield) + { + $content['fields'][$mycolkey]['FieldID'] = $mycolkey; + $content['fields'][$mycolkey]['FieldCaption'] = $content[ $myfield['FieldCaptionID'] ]; + $content['fields'][$mycolkey]['FieldType'] = $myfield['FieldType']; + $content['fields'][$mycolkey]['DefaultWidth'] = $myfield['DefaultWidth']; + + // Append to columns array + $content['AllColumns'][] = $mycolkey; + } + // --- + + // Close file! + $stream->Close(); +} +*/ +// --- + +// --- BEGIN CREATE TITLE +$content['TITLE'] = InitPageTitle(); +// Append custom title part! +$content['TITLE'] .= GetAndReplaceLangStr( $content['LN_ORACLE_TITLE'], $content['oracle_query']); +// --- END CREATE TITLE + +// --- Parsen and Output +InitTemplateParser(); +$page -> parser($content, "asktheoracle.html"); +$page -> output(); +// --- + +?> \ No newline at end of file diff --git a/src/include/functions_common.php b/src/include/functions_common.php index add4c3b..4a05fdc 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1065,10 +1065,18 @@ function AddContextLinks(&$sourceTxt) */ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) { - global $content; + global $content, $uID; // Create string - $szReturn = $prepend; + $szReturn = $prepend; + + // Set IUD property if available + if ( isset($uID) ) + $includeLinkUID = "&uid=" . $uID; + else + $includeLinkUID = ""; + + // check if it is an IP or domain if ( strlen($szIP) > 0 ) { // Split IP into array @@ -1088,14 +1096,14 @@ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) $szReturn .= '' . $szIP . ''; // Add InfoSearch Link - $szReturn .= ''; + $szReturn .= ''; } else if ( strlen($szDomain) > 0 ) { $szReturn .= '' . $szDomain . ''; // Add InfoSearch Link - $szReturn .= ''; + $szReturn .= ''; } diff --git a/src/lang/en/main.php b/src/lang/en/main.php index fefbf44..9bce6aa 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -322,5 +322,13 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; +// asktheoracle site +$content['LN_ORACLE_TITLE'] = "Asking the oracle for '%1'"; +$content['LN_ORACLE_HELP_FOR'] = "These are the links the oracle got for you"; +$content['LN_ORACLE_HELP_TEXT'] = "Spieglein Spieglein an der Wand, wer hat das größte Logfile im ganzen Land?"; +$content['LN_ORACLE_HELP_DETAIL'] = "Usefull search links for the type '%1', value = '%2'"; +$content['LN_ORACLE_SEARCHINFIELD'] = "Search in '%1' Field"; + + ?> \ No newline at end of file diff --git a/src/statistics.php b/src/statistics.php index 9fe7dd7..5763c8d 100644 --- a/src/statistics.php +++ b/src/statistics.php @@ -128,6 +128,7 @@ else // Set error code $content['ISERROR'] = true; $content['ERROR_MSG'] = GetErrorMessage(ERROR_CHARTS_NOTCONFIGURED); + $content['detailederror_code'] = ERROR_CHARTS_NOTCONFIGURED; } // --- diff --git a/src/templates/asktheoracle.html b/src/templates/asktheoracle.html new file mode 100644 index 0000000..c1e3e43 --- /dev/null +++ b/src/templates/asktheoracle.html @@ -0,0 +1,74 @@ + + + +

    +
    +
    +
    {LN_GEN_ERRORDETAILS}
    +

    {ERROR_MSG}

    +

    + + + {LN_GEN_MOREINFORMATION} + +

    +
    +

    +
    +

    + + + + + + + + + + + + + + + + + +
    {LN_ORACLE_HELP_FOR}
    + + + + +
    + {LN_DETAIL_BACKTOLIST} {LN_DETAIL_BACKTOLIST} +
    +

    {LN_ORACLE_HELP_TEXT}

    + +

    + + + + + + + + + + + + +
    {ORACLE_HELP_DETAIL}
    {SourceName} + +   + {MsgDisplayName} + + + +   + {SourceDisplayName} + +
    +

    + +
    + + \ No newline at end of file diff --git a/src/templates/statistics.html b/src/templates/statistics.html index c40ff08..42a90eb 100644 --- a/src/templates/statistics.html +++ b/src/templates/statistics.html @@ -5,7 +5,13 @@
    {LN_GEN_ERRORDETAILS}
    -

    {ERROR_MSG}

    +

    {ERROR_MSG}

    +

    + + + {LN_GEN_MOREINFORMATION} + +



    From f047d1566c134a78e58d1cb7ef470ef568265ad5 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 19 Sep 2008 11:22:52 +0200 Subject: [PATCH 133/142] Enhanced the link matrix of the asktheoracle site --- src/asktheoracle.php | 19 +++++++++++++---- src/lang/de/main.php | 13 +++++++++++ src/lang/en/main.php | 12 ++++++++--- src/lang/pt_BR/main.php | 13 +++++++++++ src/templates/asktheoracle.html | 38 +++++++++++++++++++++++++-------- 5 files changed, 79 insertions(+), 16 deletions(-) diff --git a/src/asktheoracle.php b/src/asktheoracle.php index 296da6f..d3a85fa 100644 --- a/src/asktheoracle.php +++ b/src/asktheoracle.php @@ -74,14 +74,25 @@ else // Set readable type if ( $content['oracle_type'] == "ip" ) +{ $content['oracle_type_readable'] = "ip"; + $content['oracle_kb_type'] = "ip"; +} else if ( $content['oracle_type'] == "domain" ) +{ $content['oracle_type_readable'] = "domain"; + $content['oracle_kb_type'] = "name"; +} else +{ $content['oracle_type_readable'] = "unknown type"; + $content['oracle_kb_type'] = ""; +} $content['ORACLE_HELP_DETAIL'] = GetAndReplaceLangStr( $content['LN_ORACLE_HELP_DETAIL'], $content['oracle_type_readable'], $content['oracle_query'] ) ; - +$content['ORACLE_HELP_TEXT'] = GetAndReplaceLangStr( $content['LN_ORACLE_HELP_TEXT'], $content['oracle_type_readable'], $content['oracle_query'] ) ; +$content['ORACLE_WHOIS'] = GetAndReplaceLangStr( $content['LN_ORACLE_WHOIS'], $content['oracle_type_readable'], $content['oracle_query'] ) ; +$content['WhoisUrl'] = "http://kb.monitorware.com/kbsearch.php?sa=whois&oid=" . $content['oracle_kb_type'] . "&origin=phplogcon&q=" . urlencode($content['oracle_query']); // Enable help links! $content['helplinksenabled'] = true; @@ -90,11 +101,11 @@ $content['helplinksenabled'] = true; $i = 0; foreach( $content['Sources'] as $mySource ) { - $myHelpLink['SourceName'] = GetAndReplaceLangStr("Source %1",$mySource['Name'] ); + $myHelpLink['SourceName'] = $mySource['Name']; $myHelpLink['MsgUrl'] = $content['BASEPATH'] . "index.php?filter=" . urlencode($content['oracle_query']) . "&search=Search&sourceid=" . $mySource['ID']; - $myHelpLink['MsgDisplayName'] = GetAndReplaceLangStr( $content['LN_ORACLE_SEARCHINFIELD'], "Message" ); +// $myHelpLink['MsgDisplayName'] = GetAndReplaceLangStr( $content['LN_ORACLE_SEARCHINFIELD'], "Message" ); $myHelpLink['SourceUrl'] = $content['BASEPATH'] . "index.php?filter=" . urlencode("source:=" . $content['oracle_query']) . "&search=Search&sourceid=" . $mySource['ID']; - $myHelpLink['SourceDisplayName'] = GetAndReplaceLangStr( $content['LN_ORACLE_SEARCHINFIELD'], "Source" ); +// $myHelpLink['SourceDisplayName'] = GetAndReplaceLangStr( $content['LN_ORACLE_SEARCHINFIELD'], "Source" ); // --- Set CSS Class if ( $i % 2 == 0 ) diff --git a/src/lang/de/main.php b/src/lang/de/main.php index 2010067..154c9f8 100644 --- a/src/lang/de/main.php +++ b/src/lang/de/main.php @@ -318,4 +318,17 @@ $content['LN_DETAIL_BACKTOLIST'] = "Back to Listview"; $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; + // asktheoracle site + $content['LN_ORACLE_TITLE'] = "Asking the oracle for '%1'"; + $content['LN_ORACLE_HELP_FOR'] = "These are the links the oracle got for you"; + $content['LN_ORACLE_HELP_TEXT'] = "You asked the oracle to find more information about the '%1' value '%2'.
    + The oracle has come to an answer, and brought up the following link suggestions: + "; + $content['LN_ORACLE_HELP_DETAIL'] = "Link matrix for the '%1' value '%2'"; + $content['LN_ORACLE_SEARCH'] = "Search"; // in '%1' Field"; + $content['LN_ORACLE_SOURCENAME'] = "Source name"; + $content['LN_ORACLE_FIELD'] = "Field"; + $content['LN_ORACLE_ONLINESEARCH'] = "Online Search"; + $content['LN_ORACLE_WHOIS'] = "WHOIS Lookup for '%1' value '%2'"; + ?> \ No newline at end of file diff --git a/src/lang/en/main.php b/src/lang/en/main.php index 9bce6aa..6b39384 100644 --- a/src/lang/en/main.php +++ b/src/lang/en/main.php @@ -325,9 +325,15 @@ $content['LN_CONVERT_ERROR_SOURCEIMPORT'] = "Critical Error while importing the // asktheoracle site $content['LN_ORACLE_TITLE'] = "Asking the oracle for '%1'"; $content['LN_ORACLE_HELP_FOR'] = "These are the links the oracle got for you"; -$content['LN_ORACLE_HELP_TEXT'] = "Spieglein Spieglein an der Wand, wer hat das größte Logfile im ganzen Land?"; -$content['LN_ORACLE_HELP_DETAIL'] = "Usefull search links for the type '%1', value = '%2'"; -$content['LN_ORACLE_SEARCHINFIELD'] = "Search in '%1' Field"; +$content['LN_ORACLE_HELP_TEXT'] = "You asked the oracle to find more information about the '%1' value '%2'.
    +The oracle has come to an answer, and brought up the following link suggestions: +"; +$content['LN_ORACLE_HELP_DETAIL'] = "Link matrix for the '%1' value '%2'"; +$content['LN_ORACLE_SEARCH'] = "Search"; // in '%1' Field"; +$content['LN_ORACLE_SOURCENAME'] = "Source name"; +$content['LN_ORACLE_FIELD'] = "Field"; +$content['LN_ORACLE_ONLINESEARCH'] = "Online Search"; +$content['LN_ORACLE_WHOIS'] = "WHOIS Lookup for '%1' value '%2'"; diff --git a/src/lang/pt_BR/main.php b/src/lang/pt_BR/main.php index f1dcd69..54030c2 100644 --- a/src/lang/pt_BR/main.php +++ b/src/lang/pt_BR/main.php @@ -322,4 +322,17 @@ $content['LN_DETAIL_BACKTOLIST'] = "Voltar para a lista"; $content['LN_CHART_TYPE_BARS_HORIZONTAL'] = "Bars horizontal"; $content['LN_STATS_WARNINGDISPLAY'] = "Generating graphics on large data sources currently is very time consuming. This will be addressed in later versions. If processing takes too long, please simply cancel the request."; + // asktheoracle site + $content['LN_ORACLE_TITLE'] = "Asking the oracle for '%1'"; + $content['LN_ORACLE_HELP_FOR'] = "These are the links the oracle got for you"; + $content['LN_ORACLE_HELP_TEXT'] = "You asked the oracle to find more information about the '%1' value '%2'.
    + The oracle has come to an answer, and brought up the following link suggestions: + "; + $content['LN_ORACLE_HELP_DETAIL'] = "Link matrix for the '%1' value '%2'"; + $content['LN_ORACLE_SEARCH'] = "Search"; // in '%1' Field"; + $content['LN_ORACLE_SOURCENAME'] = "Source name"; + $content['LN_ORACLE_FIELD'] = "Field"; + $content['LN_ORACLE_ONLINESEARCH'] = "Online Search"; + $content['LN_ORACLE_WHOIS'] = "WHOIS Lookup for '%1' value '%2'"; + ?> \ No newline at end of file diff --git a/src/templates/asktheoracle.html b/src/templates/asktheoracle.html index c1e3e43..d0e00f9 100644 --- a/src/templates/asktheoracle.html +++ b/src/templates/asktheoracle.html @@ -32,7 +32,7 @@
    -
    {LN_ORACLE_HELP_TEXT}

    +
    {ORACLE_HELP_TEXT}

    @@ -40,24 +40,44 @@

    - - +
    {ORACLE_HELP_DETAIL}
    + + + + +
    {LN_ORACLE_ONLINESEARCH}
    + +   {ORACLE_WHOIS} + +
    + +

    + + + + + + + + + - - + From 8f62806e5c46e7fca36b2116055eef16e54cccd4 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 19 Sep 2008 11:47:04 +0200 Subject: [PATCH 134/142] Uppercase and mixed case domain names are now also correctly detected and linked --- src/asktheoracle.php | 7 +++++++ src/include/functions_common.php | 34 +++++++++++++++++++++++++++----- src/templates/asktheoracle.html | 2 ++ src/templates/index.html | 2 +- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/asktheoracle.php b/src/asktheoracle.php index d3a85fa..c7bf8d8 100644 --- a/src/asktheoracle.php +++ b/src/asktheoracle.php @@ -77,16 +77,23 @@ if ( $content['oracle_type'] == "ip" ) { $content['oracle_type_readable'] = "ip"; $content['oracle_kb_type'] = "ip"; + + if ( IsInternalIP($content['oracle_query']) ) + $content['showonlinesearches'] = false; + else + $content['showonlinesearches'] = true; } else if ( $content['oracle_type'] == "domain" ) { $content['oracle_type_readable'] = "domain"; $content['oracle_kb_type'] = "name"; + $content['showonlinesearches'] = true; } else { $content['oracle_type_readable'] = "unknown type"; $content['oracle_kb_type'] = ""; + $content['showonlinesearches'] = false; } $content['ORACLE_HELP_DETAIL'] = GetAndReplaceLangStr( $content['LN_ORACLE_HELP_DETAIL'], $content['oracle_type_readable'], $content['oracle_query'] ) ; diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 4a05fdc..99302a6 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -1032,7 +1032,7 @@ function AddContextLinks(&$sourceTxt) if ( GetConfigSetting("EnableIPAddressResolve", 0, CFGLEVEL_USER) == 1 ) { // Search for IP's and Add Reverse Lookup first! - $sourceTxt = preg_replace( '/([^\[])\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/e', "'\\1\\2.\\3.\\4.\\5' . ReverseResolveIP('\\2.\\3.\\4.\\5', ' {', '} ')", $sourceTxt ); + $sourceTxt = preg_replace( '/([^\[])\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/ie', "'\\1\\2.\\3.\\4.\\5' . ReverseResolveIP('\\2.\\3.\\4.\\5', ' {', '} ')", $sourceTxt ); } // Create if not set! @@ -1042,8 +1042,8 @@ function AddContextLinks(&$sourceTxt) // Create Search Array $search = array ( - '/\.([\w\d\_\-]+)\.(' . $szTLDDomains . ')([^a-zA-Z0-9\.])/e', -/* (?:127)| */ '/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/e', + '/\.([\w\d\_\-]+)\.(' . $szTLDDomains . ')([^a-zA-Z0-9\.])/ie', +/* (?:127)| */ '/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/ie', ); // Create Replace Array @@ -1079,9 +1079,8 @@ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) // check if it is an IP or domain if ( strlen($szIP) > 0 ) { - // Split IP into array +/* // Split IP into array $IPArray = explode(".", $szIP); - if ( (intval($IPArray[0]) == 10 ) || (intval($IPArray[0]) == 127 ) || @@ -1089,6 +1088,8 @@ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) (intval($IPArray[0]) == 192 && intval($IPArray[1]) == 168) || (intval($IPArray[0]) == 255 ) ) +*/ + if ( IsInternalIP($szIP) ) // Do not create a LINK in this case! $szReturn .= '' . $szIP . ''; else @@ -1114,6 +1115,29 @@ function InsertLookupLink( $szIP, $szDomain, $prepend, $append ) return $szReturn; } +/* +* Helper function to check, if an IP Address is within private address space! +*/ +function IsInternalIP($szIPAddress) +{ + // Split IP into array + $IPArray = explode(".", $szIPAddress); + + if ( + (intval($IPArray[0]) == 10 ) || + (intval($IPArray[0]) == 127 ) || + (intval($IPArray[0]) == 172 && intval($IPArray[1]) >= 16 && intval($IPArray[1]) <= 31) || + (intval($IPArray[0]) == 192 && intval($IPArray[1]) == 168) || + (intval($IPArray[0]) == 255 ) + ) + + // return true in this case + return true; + else + // This is an external IP + return false; +} + /* * Reserve Resolve IP Address! */ diff --git a/src/templates/asktheoracle.html b/src/templates/asktheoracle.html index d0e00f9..a1b00d9 100644 --- a/src/templates/asktheoracle.html +++ b/src/templates/asktheoracle.html @@ -39,6 +39,7 @@ - + From 1ea9ed9c0cb9c9c3e97004dce6538d080743e05c Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 19 Sep 2008 15:00:58 +0200 Subject: [PATCH 137/142] Added new internal default view for Webserver Logfiles --- src/include/functions_config.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/include/functions_config.php b/src/include/functions_config.php index 49fcdac..b7b8e90 100644 --- a/src/include/functions_config.php +++ b/src/include/functions_config.php @@ -266,6 +266,13 @@ function InitViewConfigs() 'userid' => null, 'groupid' => null, ); + $CFG['Views']['WEBLOG']= array( + 'ID' => "WEBLOG", + 'DisplayName' =>"Webserver Fields", + 'Columns' => array ( SYSLOG_DATE, SYSLOG_HOST, SYSLOG_WEBLOG_URL, SYSLOG_WEBLOG_USERAGENT, SYSLOG_WEBLOG_STATUS, SYSLOG_WEBLOG_BYTESSEND, SYSLOG_MESSAGE ), + 'userid' => null, + 'groupid' => null, + ); // Set default of 'DefaultViewsID' $CFG['DefaultViewsID'] = "SYSLOG"; From a32e140b6962826f52e4a724ccd4dcfc8443b3ca Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 19 Sep 2008 15:24:41 +0200 Subject: [PATCH 138/142] Added changelog entry --- ChangeLog | 21 +++++++++++++++++++++ src/include/functions_common.php | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index a306124..8cd4bae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,25 @@ --------------------------------------------------------------------------- +Version 2.5.9 (devel), 2008-09-19 +- Implemented support to analyze Webserver logfiles from Apache and + Microsoft IIS Webserver. There is a collection of new fields available, + filtering is possible. . In order to read Apache or IIS logfiles, + you need to use the "iis" or "apache2" message parser in your disk + sources. +- Added a new page called "asktheoracle.php" which will be automatically + linked on IP's and domain names. It is a simple helper page which + creates a bunch of usefull filter links. +- Added some basic documentation into the doc folder. phpLogCon + does also link to this documentation now (Help Menu Entry). +- Added a general option to limit the display of string fields. +- Added a general option to configure the timeout value of popup menus. +- Added a general option to alter the php scrip timeout. This is of course + only possible if the php interpreter is allowed to change the script + timeout. +- Added checks for the script timeout in the disk logstream, this avoids + that the script is suddenly stopped by the php interpreter. +- Fixed a sorting bug in the disk logstream, which removed numeric + values in the charts +--------------------------------------------------------------------------- Version 2.5.8 (devel), 2008-09-16 - Added Bitstream Vera Fonts into the package which will be used by the chart generator. So there won't be a problem of missing truetype fonts diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 99302a6..1218795 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -66,7 +66,7 @@ $LANG_EN = "en"; // Used for fallback $LANG = "en"; // Default language // Default Template vars -$content['BUILDNUMBER'] = "2.5.8"; +$content['BUILDNUMBER'] = "2.5.9"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! From 2b7d1d790bb02f96dfcba57c5da0a9fdcbfc642f Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 19 Sep 2008 15:26:26 +0200 Subject: [PATCH 139/142] Added something to the changelog --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index 8cd4bae..06afe4a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,9 @@ Version 2.5.9 (devel), 2008-09-19 that the script is suddenly stopped by the php interpreter. - Fixed a sorting bug in the disk logstream, which removed numeric values in the charts +- Added support to filter for a + character as well now. In order to filter + for strings containing the + character, just add replace it with ++. + A single + will still be equal to a space character in the filter engine. --------------------------------------------------------------------------- Version 2.5.8 (devel), 2008-09-16 - Added Bitstream Vera Fonts into the package which will be used by the From 7bf38af1a45d3d858f0831bf467ef31082896d59 Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Fri, 19 Sep 2008 17:10:29 +0200 Subject: [PATCH 140/142] Enhanced search performance in disk logstream, specially when filtering in LARGE files! --- src/classes/logstream.class.php | 2 +- src/classes/logstreamdisk.class.php | 10 ++++++---- src/index.php | 9 ++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/classes/logstream.class.php b/src/classes/logstream.class.php index 4ceafaf..4b4399f 100644 --- a/src/classes/logstream.class.php +++ b/src/classes/logstream.class.php @@ -656,8 +656,8 @@ abstract class LogStream { // "+", ); - $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] = preg_replace( $searchArray, $replaceArray, $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] ); // $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] = str_replace( '+', ' ', $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE]); + $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] = preg_replace( $searchArray, $replaceArray, $this->_filters[$tmpKeyName][$iNum][FILTER_VALUE] ); // --- } diff --git a/src/classes/logstreamdisk.class.php b/src/classes/logstreamdisk.class.php index 3eefe23..c7d2a0a 100644 --- a/src/classes/logstreamdisk.class.php +++ b/src/classes/logstreamdisk.class.php @@ -234,7 +234,7 @@ class LogStreamDisk extends LogStream { $ret = $this->ReadNextForwards($uID, $arrProperitesOut); else $ret = $this->ReadNextBackwards($uID, $arrProperitesOut); - + // Only PARSE on success! if ( $ret == SUCCESS && $bParseMessage) { @@ -243,7 +243,7 @@ class LogStreamDisk extends LogStream { // Run optional Message Parsers now $retParser = $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut); - + // Check if we have to skip the message! if ( $retParser == ERROR_MSG_SKIPMESSAGE ) $ret = $retParser; @@ -523,8 +523,10 @@ class LogStreamDisk extends LogStream { */ public function GetLastPageUID() { - // Obtain last UID if enough records are available! - + // Only perform lastUID scan if there are NO filters, for performance REASONS! + if ( $this->_filters != null ) + return UID_UNKNOWN; + // Helper variables $myuid = -1; $counter = 0; diff --git a/src/index.php b/src/index.php index 3dda9f1..26d30b9 100644 --- a/src/index.php +++ b/src/index.php @@ -230,10 +230,6 @@ if ( isset($content['Sources'][$currentSourceID]) ) // TODO Implement ORDER $stream->SetReadDirection($content['read_direction']); - // Read First and LAST UID's before start reading the stream! - $content['uid_last'] = $stream->GetLastPageUID(); - $content['uid_first'] = $stream->GetFirstPageUID(); - // Set current ID and init Counter $uID = $content['uid_current']; $counter = 0; @@ -830,9 +826,12 @@ if ( isset($content['Sources'][$currentSourceID]) ) // Increment Counter $counter++; } while ($counter < $content['ViewEntriesPerPage'] && ($ret = $stream->ReadNext($uID, $logArray)) == SUCCESS); - //print_r ( $content['syslogmessages'] ); + // Move below processing - Read First and LAST UID's before start reading the stream! + $content['uid_last'] = $stream->GetLastPageUID(); + $content['uid_first'] = $stream->GetFirstPageUID(); + if ( $content['main_recordcount'] == -1 || $content['main_recordcount'] > $content['ViewEntriesPerPage'] ) { // Enable Pager in any case here! From e1db49c25595140dcb6cb4a86ac94297e595902b Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 24 Sep 2008 14:29:36 +0200 Subject: [PATCH 141/142] Enhanced critical error display, and added better error details if the user db server is not reachable --- src/include/functions_common.php | 37 ++++++++++++++++++++++++-------- src/include/functions_db.php | 14 +++++++++--- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/include/functions_common.php b/src/include/functions_common.php index 1218795..dd4e8a3 100644 --- a/src/include/functions_common.php +++ b/src/include/functions_common.php @@ -70,9 +70,12 @@ $content['BUILDNUMBER'] = "2.5.9"; $content['TITLE'] = "phpLogCon :: Release " . $content['BUILDNUMBER']; // Default page title $content['BASEPATH'] = $gl_root_path; $content['SHOW_DONATEBUTTON'] = true; // Default = true! + +// PreInit overall user variables $content['EXTRA_METATAGS'] = ""; $content['EXTRA_JAVASCRIPT'] = ""; $content['EXTRA_STYLESHEET'] = ""; +$content['CURRENTURL'] = ""; // --- // --- Check PHP Version! If lower the 5, phplogcon will not work proberly! @@ -798,20 +801,36 @@ function CheckUrlOrIP($ip) function DieWithErrorMsg( $szerrmsg ) { global $gl_root_path, $content; - print("phpLogCon :: Critical Error occured"); - print("
    {ORACLE_HELP_DETAIL}
    {LN_ORACLE_SOURCENAME}{LN_ORACLE_FIELD} '{LN_FIELDS_MESSAGE}'{LN_ORACLE_FIELD} '{LN_FIELDS_HOST}' 
    {SourceName} + -   - {MsgDisplayName} +   + {LN_ORACLE_SEARCH} + -   - {SourceDisplayName} +   + {LN_ORACLE_SEARCH}  
    +

    @@ -50,6 +51,7 @@
    {LN_ORACLE_ONLINESEARCH}
    +

    diff --git a/src/templates/index.html b/src/templates/index.html index 16c6b41..1e05ac3 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -245,7 +245,7 @@ -
    +

    {ORACLE_HELP_TEXT}

    {ORACLE_HELP_TEXT}

    Critical Error occured


    "); - print("Errordetails:
    " . $szerrmsg); - print("
    "); - + echo + "phpLogCon :: Critical Error occured" . + "

    " . + "". + "" . + "" . + "
    " . + "

    Critical Error occured

    " . + "
    Errordetails:" . + $szerrmsg . + "
    " . + ""; exit; } function DieWithFriendlyErrorMsg( $szerrmsg ) { - //TODO: Make with template - print(""); - print("

    Error occured


    "); - print("Errordetails:
    " . $szerrmsg); + global $gl_root_path, $content; + echo + "phpLogCon :: Error occured" . + "

    " . + "". + "" . + "" . + "
    " . + "

    Error occured

    " . + "
    Errordetails:" . + $szerrmsg . + "
    " . + ""; exit; } diff --git a/src/include/functions_db.php b/src/include/functions_db.php index d9e0ce2..97ab4d1 100644 --- a/src/include/functions_db.php +++ b/src/include/functions_db.php @@ -57,10 +57,18 @@ function DB_Connect() if ($userdbconn) return; - //TODO: Check variables first - $userdbconn = mysql_connect( GetConfigSetting("UserDBServer"), GetConfigSetting("UserDBUser"), GetConfigSetting("UserDBPass")); + $userdbconn = @mysql_connect( GetConfigSetting("UserDBServer"), GetConfigSetting("UserDBUser"), GetConfigSetting("UserDBPass")); if (!$userdbconn) - DB_PrintError("Link-ID == false, connect to " . GetConfigSetting("UserDBServer") . " failed", true); + { + // Create Error Msg + $szErrorMsg = "Failed to establish a connection to the configured MYSQL Server.
    PhpLogCon is not able to initialize the user system."; + if ( isset($php_errormsg) ) + $szErrorMsg .= "

    Extra Error Details:
    " . $php_errormsg; + + DieWithErrorMsg( $szErrorMsg ); + } + + //TODO: Check variables first // --- Now, check Mysql DB Version! $strmysqlver = mysql_get_server_info(); From ff64d05e4283a2e3e3dc37645c380e3772ee0c1a Mon Sep 17 00:00:00 2001 From: Andre Lorbach Date: Wed, 24 Sep 2008 15:27:19 +0200 Subject: [PATCH 142/142] Removed unused files --- src/js/admin-menuoptionen.js | 31 -- src/js/menu_dom.js | 984 ----------------------------------- src/js/menu_misc.js | 285 ---------- src/js/menu_ns4.js | 678 ------------------------ 4 files changed, 1978 deletions(-) delete mode 100644 src/js/admin-menuoptionen.js delete mode 100644 src/js/menu_dom.js delete mode 100644 src/js/menu_misc.js delete mode 100644 src/js/menu_ns4.js diff --git a/src/js/admin-menuoptionen.js b/src/js/admin-menuoptionen.js deleted file mode 100644 index 16b75d7..0000000 --- a/src/js/admin-menuoptionen.js +++ /dev/null @@ -1,31 +0,0 @@ -var navigationsname = 'awmenu10'; // nicht ändern !!!! -var hintergrundfarbe = '#F5F5F5'; // Standardfarbwert = #FFFFFF -var rahmenfarbe = '#999999'; // -var onmouseoverhintergrund = '#E0E0E0'; // Standardfarbwert = #E0E0E0 -var onmouseoverschriftfarbe = '#000099'; // Standardfarbwert = #FF0000 -var menupunktschriftfarbe = '#000033'; // Standardfarbwert = #000000 -var rahmenbreite = 1; // Breite des Rahmens -var schriftart = 'Verdana, Tahoma, Arial'; // Arial, Verdana, Times, Courier, Georgia, Geneva -var bilderpfad = '../images/misc/'; // Bilderpfad -var pfeilrunter = 'transparent.gif'; // Name des -nach-unten Pfeils- -var pfeilrechts = 'transparent.gif'; // Name des -nach-rechts Pfeils- -var verzoegerung = 500; // Untermenu verschwindet nach X Millisekunden -var tabellenausrichtung_x = 1; // 0 = links ; 1 = mitte ; 2 = rechts -var tabellenausrichtung_y = 0; // 0 = oben ; 1 = mitte ; 2 = unten -var h_offset = 0; // Tabellenfeinjustierung (x-achse) -var v_offset = 50; // Tabellenfeinjustierung (y-achse) -var menuausrichtung = 1; // 0 = vertikal ; 1 = horizontal -var schriftgroesse = 12; // Schriftgroesse -> Standard = 8 -var schriftausrichtung = 0; // 0 = links ; 1 = mitte -> HORIZONTAL -var spaltenhoehe = 15; // Spaltenhoehe -> Standard = 15 -var spaltenbreite = 110; // Spaltenbreite -> Standard = 140 -var submenu_offset_x = 0; // Submenu Abstand - X vom Hauptmenu -var submenu_offset_y = 17; // Submenu Abstand - Y vom Hauptmenu -var item_offset_h = 3; // Schriftfeinjustierung (x-achse) -var item_offset_v = 0; // Schriftfeinjustierung (y-achse) -var itemtrenner = 1; // 0 = kein Trenner d ; 1 = Trenner -var fontstyle = "normal"; // normal, bold -var frameausrichtung = 0; // 0 = horizontal ; 1 = vertikal -var mainmenuframename = ''; // Name des Hauptframes (keine Verwendung) -var submenuframename = ''; // Name des Unterframes (keine Verwendung) -var targetframename = ''; // Name des Zielframes (keine Verwendung) diff --git a/src/js/menu_dom.js b/src/js/menu_dom.js deleted file mode 100644 index 7cf1653..0000000 --- a/src/js/menu_dom.js +++ /dev/null @@ -1,984 +0,0 @@ -/* -Javscript menu -IE & Others Stuff -*/ - -inDragMode=0; -_d.onmousemove=getMouseXY; -_flta="return false"; -if(ie55)_flta="try{if(ap.filters){return 1}}catch(e){}"; -_d.write("<"+"script>function getflta(ap){"+_flta+"}<"+"/script>"); -_mot=0; -gevent=0; -_ifc=0; - - -function $CtI($ti) -{ - clearTimeout($ti) -} - -function getMouseXY(e) -{ - if(ns6) - { - MouseX=e.pageX; - MouseY=e.pageY - } - else - { - MouseX=event.clientX; - MouseY=event.clientY - } - if(!op&&_d.all&&_d.body) - { - MouseX=MouseX+_d.body.scrollLeft; - MouseY=MouseY+_d.body.scrollTop; - - if(IEDtD&&!mac) - { - MouseY=MouseY+_sT; - MouseX=MouseX+_sL; - } - } - if(inDragMode) - { - gm=gmobj(DragLayer); - spos(gm,MouseY-DragY,MouseX-DragX); - return false - } - return _t; -} - -function gmobj(_mtxt) -{ - if(_d.getElementById){return _d.getElementById(_mtxt)}else if(_d.all){return _d.all[_mtxt]} -} - -function spos(_gm,_t,_l,_h,_w) -{ - _px="px"; - if(op){_px=""; - _gs=_gm.style; - if(_w!=_n)_gs.pixelWidth=_w; - if(_h!=_n)_gs.pixelHeight=_h}else{_gs=_gm.style; - if(_w!=_n)_gs.width=_w+_px; - if(_h!=_n)_gs.height=_h+_px}if(_t!=_n)_gs.top=_t+_px; - if(_l!=_n)_gs.left=_l+_px}function gpos(_gm){_h=_gm.offsetHeight; - _w=_gm.offsetWidth; - if(op5){_h=_gm.style.pixelHeight; - _w=_gm.style.pixelWidth}_tgm=_gm; - _t=0; - while(_tgm!=_n){_t+=_tgm.offsetTop; - _tgm=_tgm.offsetParent}_tgm=_gm; - _l=0; - while(_tgm!=_n){_l+=_tgm.offsetLeft; - _tgm=_tgm.offsetParent} - - if(sfri) - { _l-=_d.body.offsetLeft; - _t-=_d.body.offsetTop - } - if(mac&&!mac45){if(_macffs=_d.body.currentStyle.marginTop){_t=_t+parseInt(_macffs)}if(_macffs=_d.body.currentStyle.marginLeft){_l=_l+parseInt(_macffs)}}_gpa=new Array(_t,_l,_h,_w); - return(_gpa) -} - -function applyFilter(_gm,_mnu) -{ - if(getflta(_gm)){if(_gm.style.visibility=="visible")flt=_m[_mnu][16]; - else flt=_m[_mnu][15]; - if(flt){if(_gm.filters[0])_gm.filters[0].stop(); - iedf=""; - iedf="FILTER:"; - flt=flt.split(";"); - for(fx=0; - fx0)fx=999}_gm.style.filter=iedf; - _gm.filters[0].apply()}}}function playFilter(_gm,_mnu){if(getflta(_gm)){if(_gm.style.visibility=="visible")flt=_m[_mnu][15]; - else flt=_m[_mnu][16]; - if(flt)_gm.filters[0].play()} -} - -function menuDisplay(_mnu,_show) -{ - _gmD=gmobj("menu"+_mnu); - if(!_gmD)return; - _m[_mnu][22]=_gmD; - M_hideLayer(_mnu,_show); - if(_show){if(_m[_mnu][21]>-1&&_m[_mnu][21]!=_itemRef){itemOff(_m[_mnu][21]); - _m[_mnu][21]=_itemRef}if(_m[_mnu][7]==0&&_ofMT==1)return; - if(_gmD.style.visibility.toUpperCase()!="VISIBLE"){_SoT(_mnu,1); - applyFilter(_gmD,_mnu); - if(!_m[_mnu][7]&&!_m[_mnu][14]&&ns6)_gmD.style.position="fixed"; - _gmD.style.zIndex=_zi; - _gmD.style.visibility="visible"; - playFilter(_gmD,_mnu); - if(!_m[_mnu][7])_m[_mnu][21]=_itemRef; - _mnuD++}}else{if(_m[_mnu][21]>-1&&_itemRef!=_m[_mnu][21])itemOff(_m[_mnu][21]); - if(_gmD.style.visibility.toUpperCase()=="VISIBLE"){_SoT(_mnu,0); - applyFilter(_gmD,_mnu); - if(!_m[_mnu][14]&&ns6)_gmD.style.position="absolute"; - _gmD.style.visibility="hidden"; - if(mac||konq){_gmD.style.top="-999px"; - _gmD.style.left="-999px"}playFilter(_gmD,_mnu); - _mnuD--}_m[_mnu][21]=-1} -} - -function closeAllMenus() -{ - if(_oldel>-1)itemOff(_oldel); - _oldel=-1; - for(_a=0; - _a<_m.length; - _a++){if(!_m[_a][7]&&!_m[_a][10])menuDisplay(_a,0)}_mnuD=0; - _zi=999; - _itemRef=-1}_lcC=0; - function _lc(_i){_I=_mi[_i]; - _lcC++; - if(_I[62]&&_lcC==1)eval(_I[62]); - if(_I[34]=="disabled")return; - _feat=""; - if(_I[57])_feat=_I[57]; - if(op||_feat||(sfri||ns6||konq||mac45)){_trg=""; - if(_I[35])_trg=_I[35]; - if(_trg)window.open(_I[2],_trg,_feat); - else(location.href=_I[2])}else{_gm=gmobj("lnk"+_i); - _gm.href=_I[2]; - _gm.click()}closeAllMenus(); - if(_lcC==2)_lcC=0; -} - -function getMenuByItem(_gel) -{ - _gel=_mi[_gel][0]; - if(_m[_gel][7])_gel=-1; - return _gel; -} - -function getParentMenuByItem(_gel) -{ - _tm=getMenuByItem(_gel); - if(_tm==-1)return-1; - for(_x=0; - _x<_mi.length; - _x++){if(_mi[_x][3]==_m[_tm][1]){return _mi[_x][0]}} -} - -function getParentItemByItem(_gel) -{ - _tm=getMenuByItem(_gel); - if(_tm==-1)return-1; - for(_x=0; - _x<_mi.length; - _x++){if(_mi[_x][3]==_m[_tm][1]){return _x;}} -} - -function getMenuByName(_mn) -{ - _mn=$tL(_mn); - for(_xg=0;_xg<_m.length;_xg++) - { - if(_mn==_m[_xg][1]) - return _xg; - } -} - -function itemOn(_i) -{ - $CtI(_mot); - _mot=null; - _gmi=gmobj("el"+_i); - if(_gmi.itemOn==1)return; - _gmi.itemOn=1; - _gmt=gmobj("tr"+_i); - var _I=_mi[_i]; - if(_I[34]=="header")return; - if(_gmt){_gmt=gmobj("tr"+_i); - _gs=_gmt.style; - if(_I[53])_gmt.className=_I[53]}else{_gs=_gmi.style}if(_I[2]||_I[3]){_mP=(ns6)?"pointer":"hand"; - if(_I[59])_mP=_I[59]; - _gs.cursor=_mP; - if(_I[29])gmobj("img"+_i).style.cursor=_gs.cursor; - if(_I[24]&&_I[3])gmobj("simg"+_i).style.cursor=_gs.cursor}if(_I[32]&&_I[29]){gmobj("img"+_i).src=_I[32]}if(_I[3]&&_I[24]&&_I[48]){gmobj("simg"+_i).src=_I[48]}if(_I[53])_gmi.className=_I[53]; - if(_I[6])_gs.color=_I[6]; - if(_I[5])_gmi.style.background=_I[5]; - if(_I[47]){_oi="url("+_I[47]+")"; - if(_gmi.style.backgroundImage!=_oi); - _gmi.style.backgroundImage=_oi}if(_I[26])_gs.textDecoration=_I[26]; - if(!mac){if(_I[44])_gs.fontWeight="bold"; - if(_I[45])_gs.fontStyle="italic"}if(_I[42])eval(_I[42]); - if(_I[25]){_gmi.style.border=_I[25]; - if(!_I[9])_gs.padding=_I[11]-parseInt(_gmi.style.borderWidth)+"px";} -} - -function itemOff(_i) -{ - _gmi=gmobj("el"+_i); - if(_gmi.itemOn==0)return; - _gmi.itemOn=0; - _gmt=gmobj("tr"+_i); - var _I=_mi[_i]; - if(_I[32]&&_I[29]){gmobj("img"+_i).src=_I[29]}if(_I[3]&&_I[24]&&_I[48]){gmobj("simg"+_i).src=_I[24]}if(_I[4]&&_I[4]!="none")window.status=""; - if(_i==-1)return; - if(_gmt){_gmt=gmobj("tr"+_i); - _gs=_gmt.style; - if(_I[54])_gmt.className=_I[54]}else{_gs=_gmi.style}if(_I[54])_gmi.className=_I[54]; - if(_I[46])_gmi.style.backgroundImage="url("+_I[46]+")"; - else if(_I[7])_gmi.style.background=_I[7]; - if(_I[8])_gs.color=_I[8]; - if(_I[26])_gs.textDecoration="none"; - if(_I[33])_gs.textDecoration=_I[33]; - if(!mac){if(_I[44]&&(_I[14]=="normal"||!_I[14]))_gs.fontWeight="normal"; - if(_I[45]&&(_I[13]=="normal"||!_I[13]))_gs.fontStyle="normal"}if(!_startM&&_I[43])eval(_I[43]); - if(_I[25]){_gmi.style.border="0px"; - if(!_I[9])_gs.padding=_I[11]+"px"}if(_I[9]){_gmi.style.border=_I[9];} -} - -function closeMenusByArray(_cmnu) -{ - for(_a=0; - _a<_cmnu.length; - _a++)if(_cmnu[_a]!=_mnu)if(!_m[_cmnu[_a]][7])menuDisplay(_cmnu[_a],0); -} - -function getMenusToClose() -{ - _st=-1; - _en=_sm.length; - _mm=_iP; - if(_iP==-1){if(_sm[0]!=_masterMenu)return _sm; - _mm=_masterMenu}for(_b=0; - _b<_sm.length; - _b++){if(_sm[_b]==_mm)_st=_b+1; - if(_sm[_b]==_mnu)_en=_b}if(_st>-1&&_en>-1){_tsm=_sm.slice(_st,_en)}return _tsm}function _cm(){_tar=getMenusToClose(); - closeMenusByArray(_tar); - for(_b=0; - _b<_tar.length; - _b++){if(_tar[_b]!=_mnu)_sm=remove(_sm,_tar[_b]);} -} - -function _getDims() -{ - if(!op&&_d.all){_mc=_d.body; - if(IEDtD&&!mac&&!op7)_mc=_d.documentElement; - if(!_mc)return; - _bH=_mc.clientHeight; - _bW=_mc.clientWidth; - _sT=_mc.scrollTop; - _sL=_mc.scrollLeft}else{_bH=window.innerHeight; - _bW=window.innerWidth; - if(ns6){if(_d.documentElement.offsetWidth!=_bW){_bW=_bW-15}}_sT=self.scrollY; - _sL=self.scrollX; - if(op){_sT=_d.body.scrollTop; - _sL=_d.body.scrollleft}}}function c_openMenu(_i){var _I=_mi[_i]; - if(_I[3]){_oldMC=_I[39]; - _I[39]=0; - _oldMD=_menuOpenDelay; - _menuOpenDelay=0; - _gm=gmobj("menu"+getMenuByName(_I[3])); - if(_gm.style.visibility=="visible"&&_I[40]){menuDisplay(getMenuByName(_I[3]),0); - itemOn(_i)}else{_popi(_i)}_menuOpenDelay=_oldMD; - _I[39]=_oldMC}else{if(_I[2]&&_I[39])eval(_I[2]);} -} - -function getOffsetValue(_ofs) -{ - _ofsv=null; - if(_ofs) - {_ofsv=_ofs;} - if(isNaN(_ofs)&&_ofs.indexOf("offset=")==0) - {_ofsv=parseInt(_ofs.substr(7,99));} - return _ofsv; -} - -function popup() - { - _sm=new Array; - _arg=arguments; - $CtI(_MT); - _MT=null; - $CtI(_oMT); - _oMT=null; - closeAllMenus(); - if(_arg[0]){_ofMT=0; - _mnu=getMenuByName(_arg[0]); - if(ie4)_fixMenu(_mnu); - _tos=0; - if(_arg[2])_tos=_arg[2]; - _los=0; - if(_arg[3])_los=_arg[3]; - _sm[_sm.length]=_mnu; - if(ns6&&!ns60){_los-=_sL; - _tos-=_sT; - _gm=gmobj("menu"+_mnu); - _gp=gpos(_gm); - spos(_gm,_m[_mnu][2]+_tos,_m[_mnu][3]+_los)}if(mac)spos(gmobj("menu"+_mnu),_m[_mnu][2],_m[_mnu][3]); - if(_arg[1]){_gm=gmobj("menu"+_mnu); - if(!_gm)return; - _gp=gpos(_gm); - if(_arg[1]==1){if(MouseY+_gp[2]>(_bH+_sT))_tos=-(MouseY+_gp[2]-_bH)+_sT; - if(MouseX+_gp[3]>(_bW+_sL))_los=-(MouseX+_gp[3]-_bW)+_sL; - if(_m[_mnu][2]){if(isNaN(_m[_mnu][2]))_tos=getOffsetValue(_m[_mnu][2]); - else{_tos=_m[_mnu][2]; - MouseY=0}}if(_m[_mnu][3]){if(isNaN(_m[_mnu][3]))_los=getOffsetValue(_m[_mnu][3]); - else{_los=_m[_mnu][3]; - MouseX=0}}spos(_gm,(MouseY+_tos),(MouseX+_los))}else{_po=gmobj(_arg[1]); - _pp=gpos(_po); - spos(_gm,_pp[0]+_pp[2]+getOffsetValue(_m[_mnu][2])+_tos,_pp[1]+getOffsetValue(_m[_mnu][3])+_los)}}_zi=_zi+100; - if(_m[_mnu][13]=="scroll")_check4Scroll(_mnu); - menuDisplay(_mnu,1); - _m[_mnu][21]=-1;} -} - -function popdown() -{ - _MT=setTimeout("closeAllMenus()",_menuCloseDelay) -} - -function _popi(_i) -{ - var _I=_mi[_i]; - if(!_I)return; - _pMnu=_m[_I[0]]; - $CtI(_MT); - _MT=null; - if(_oldel>-1){gm=0; - if(_I[3]){gm=gmobj("menu"+getMenuByName(_I[3])); - if(gm&&gm.style.visibility.toUpperCase()=="VISIBLE"&&_i==_oldel){itemOn(_i); - return}}if(_oldel!=_i)itemOff(_oldel); - $CtI(_oMT); - _oMT=null}$CtI(_cMT); - _cMT=null; - _mnu=-1; - _itemRef=_i; - if(_I[34]=="disabled")return; - _mopen=_I[3]; - horiz=0; - if(_pMnu[9])horiz=1; - itemOn(_i); - if(!_sm.length){_sm[_sm.length]=_I[0]; - _masterMenu=_I[0]}_iP=getMenuByItem(_i); - if(_iP==-1)_masterMenu=_I[0]; - if(_I[4]!="none"){if(_I[4]!=null)window.status=_I[4]; - else if(_I[2])window.status=_I[2]}_cMT=setTimeout("_cm()",_menuOpenDelay); - if(_I[39]){if(_mopen){_mnu=getMenuByName(_mopen); - _gm=gmobj("menu"+_mnu); - if(_gm.style.visibility.toUpperCase()=="VISIBLE"){$CtI(_cMT); - _cMT=null; - _tsm=_sm[_sm.length-1]; - if(_tsm!=_mnu)menuDisplay(_tsm,0)}}}if(!window.retainClickValue)inopenmode=0; - if(_mopen&&(!_I[39]||inopenmode)&&_I[34]!="tree"){_getDims(); - _pm=gmobj("menu"+_I[0]); - _pp=gpos(_pm); - _mnu=getMenuByName(_mopen); - if(_I[41])_m[_mnu][10]=1; - if(ie4||op||konq)_fixMenu(_mnu); - if(_mnu>-1){if(_oldel>-1&&(_mi[_oldel][0]+_I[0]))menuDisplay(_mnu,0); - _oMT=setTimeout("menuDisplay("+_mnu+",1)",_menuOpenDelay); - _mnO=gmobj("menu"+_mnu); - _mp=gpos(_mnO); - if(ie4){_mnT=gmobj("tbl"+_mnu); - _tp=gpos(_mnT); - _mp[3]=_tp[3]}_gmi=gmobj("el"+_i); - if(!horiz&&mac)_gmi=gmobj("pTR"+_i); - _gp=gpos(_gmi); - if(horiz){_left=_gp[1]; - _top=_pp[0]+_pp[2]-_I[65]}else{_left=_pp[1]+_pp[3]-_I[65]; - _top=_gp[0]}if(sfri){if(_pMnu[14]=="relative"){_left=_left+_d.body.offsetLeft; - _top=_top+_d.body.offsetTop}}if(_pMnu[13]=="scroll"&&!op&&!mac45&&!sfri&&!konq){if(ns6&&!ns7)_top=_top-gevent; - else _top=_top-_pm.scrollTop}if(_m[_mnu][2]!=_n){if(isNaN(_m[_mnu][2])&&_m[_mnu][2].indexOf("offset=")==0){_top=_top+getOffsetValue(_m[_mnu][2])}else{_top=_m[_mnu][2]}}if(_m[_mnu][3]!=_n){if(isNaN(_m[_mnu][3])&&_m[_mnu][3].indexOf("offset=")==0){_left=_left+getOffsetValue(_m[_mnu][3])}else{_left=_m[_mnu][3]}}if(!horiz&&(_top+_mp[2]+20)>(_bH+_sT)){_top=(_bH-_mp[2])+_sT-16}if(_left+_mp[3]>_bW+_sL){if(!horiz&&(_pp[1]-_mp[3])>0){_left=_pp[1]-_mp[3]-_subOffsetLeft+_pMnu[6][65]}else{_left=(_bW-_mp[3])-8}}if(horiz){if(_m[_mnu][11]=="rtl")_left=_left-_mp[3]+_gp[3]+2; - if(_pMnu[5]&&_pMnu[5].indexOf("bottom")!=-1){_top=_pp[0]-_mp[2]-1}}else{if(_m[_mnu][11]=="rtl")_left=_pp[1]-_mp[3]-(_subOffsetLeft*2); - _top+=_subOffsetTop; - _left+=_subOffsetLeft}if(_left<2)_left=2; - if(_top<2)_top=2; - if(ns60){_left-=+_pMnu[6][65]; - _top-=+_pMnu[6][65]}if(mac){_left-=_pMnu[12]+_pMnu[6][65]; - _top-=_pMnu[12]+_pMnu[6][65]}if(sfri||op){if(!horiz){_top-=_pMnu[6][65]}else{_left-=_pMnu[6][65]}}if(_m[_I[0]][7]&&(ns6||ns7))_top=_top-_sT; - spos(_mnO,_top,_left); - if(_m[_mnu][5])_setPosition(_mnu); - if(_m[_mnu][13]=="scroll")_check4Scroll(_mnu); - _zi++; - _mnO.style.zIndex=_zi; - if(_sm[_sm.length-1]!=_mnu)_sm[_sm.length]=_mnu}}_setPath(_iP); - _oldel=_i; - _ofMT=0}function _check4Scroll(_mnu){if(op)return; - _M=_m[_mnu]; - gm=gmobj("menu"+_mnu); - _gp=gpos(gm); - gmt=gmobj("tbl"+_mnu); - _gt=gpos(gmt); - _MS=_mi[_M[0][0]]; - _cor=(_M[12]*2)+(_MS[65]*2); - _sdim=_gt[2]+_sT; - if(horiz)_sdim=_gt[2]+_gt[0]-_sT; - if(_m[_mnu][2]&&!isNaN(_m[_mnu][2]))_sdim=_m[_mnu][2]+_gt[2]; - if(_sdim<(_bH+_sT)){gm.style.overflow=""; - _top=_n; - if(!horiz&&(_gt[0]+_gt[2]+16)>(_bH+_sT)){_top=(_bH-_gt[2])+_sT-16}_ofx=0; - if(op7)_ofx=_cor; - _ofy=0; - if(mac)_ofy=_cor; - spos(gm,_top,_n,_gt[2]+_ofy,_gt[3]+_ofx); - return}gm.style.overflow="auto"; - _sbw=_gt[3]; - if(mac){if(IEDtD)_sbw=_sbw+16; - else _sbw=_sbw+16+_cor; - _btm=gmobj("btm"+_mnu); - _btm.style.height=_M[12]*2+"px"}else if(IEDtD){if(op7){_sbw=_sbw+16}else{_sbw+=_d.documentElement.offsetWidth-_d.documentElement.clientWidth-3}}else{if(op7){_sbw=_sbw+16+_cor}else{_sbw+=_d.body.offsetWidth-_d.body.clientWidth-4+_cor}if(ie4)_sbw=_gt[3]+16+_cor; - if(ns6||sfri){_sbw=_gt[3]+15; - if(!navigator.vendor)_sbw=_sbw+4}}_top=_n; - if(horiz){_ht=_bH-_gt[0]-16+_sT}else{_ht=_bH-16; - _top=6+_sT}_left=_n; - if(_gp[1]+_sbw>(_bW+_sL)){_left=(_bW-_sbw)-2}if(_m[_mnu][2]&&!isNaN(_m[_mnu][2])){_top=_m[_mnu][2]; - _ht=_bH-_top-6}if(_ht>0)spos(gm,_top,_left,_ht+2,_sbw)}function _setPath(_mpi){if(_mpi>-1){_ci=_m[_mpi][21]; - while(_ci>-1){itemOn(_ci); - _ci=_m[_mi[_ci][0]][21]}}}function _CAMs(){_MT=setTimeout("_AClose()",_menuCloseDelay); - $CtI(_oMT); - _oMT=null; - _ofMT=1; -} - -function _AClose() -{ - if(_ofMT==1) - { - closeAllMenus(); - inopenmode=0; - } -} - -function _setCPage(_i) -{ - if(_i[18])_i[8]=_i[18]; - if(_i[19])_i[7]=_i[19]; - if(_i[56]&&_i[29])_i[29]=_i[56]; - if(_i[69])_i[46]=_i[69]; - if(_i[48]&&_i[3])_i[24]=_i[48]; - if(_i[25])_i[9]=_i[25]; - if(_i[72])_i[54]=_i[72] -} - -function _getCurrentPage() -{ - _I=_mi[_el]; - if(_I[2]){_url=_I[2]; - _hrf=location.href; - fstr=_hrf.substr((_hrf.length-_url.length),_url.length); - if(fstr==_url){_setCPage(_I); - _cip[_cip.length]=_el;}} -} - -function _oifx(_i) -{ - _G=gmobj("simg"+_i); - spos(_G,_n,_n,_G.height,_G.width); - spos(gmobj("el"+_i),_n,_n,_G.height,_G.width); -} - -function _getLink(_I,_gli) -{ - _link=""; - actiontext+=" onMouseOver=\"_popi("+_gli+")\" onclick=\"opentree();"; - if(_I[2]){_targ=""; - if(_I[35])_targ="target="+_I[35]; - _link=""; - actiontext+="_lc("+_gli+");c_openMenu("+_gli+")"}actiontext+="\""; - return _link; -} - -function drawItem(_i) -{ - _I=_mi[_el]; - _mnu=_I[0]; - var _M=_m[_mnu]; - _getCurrentPage(); - if(_I[34]=="header") - { - if(_I[20]) - _I[8]=_I[20]; - if(_I[21]) - _I[7]=_I[21]; - } - _ofb=(_I[46]?"background-image:url("+_I[46]+");":""); - if(!_ofb) - _ofb=(_I[7]?"background:"+_I[7]:""); - - _ofc=(_I[8]?"color:"+_I[8]:""); - _fsize=(_I[12]?";font-Size:"+_I[12]:""); - _fstyle=(_I[13]?";font-Style:"+_I[13]:""); - _fweight=(_I[14]?";font-Weight:"+_I[14]:""); - _ffam=(_I[15]?";font-Family:"+_I[15]:""); - _tdec=""; - - if(_I[33]) - _tdec=";text-Decoration:"+_I[33]; - actiontext=" onmouseout=_mot=setTimeout(\"itemOff("+_el+")\",100) "; - _link=""; - if(_I[39]) - { - actiontext+=" onclick=\"inopenmode=1;c_openMenu("+_el+");\" onMouseOver=\"_popi("+_el+");\""; - } - else - { - _link=_getLink(_I,_el); - } - if(_I[34]=="dragable") - actiontext+=" onmousedown=\"drag_drop('menu"+_mnu+"')\""; - - _clss=""; - if(_I[54]) - _clss="class="+_I[54]; - if(horiz) - { - if(_i==0) - _mt+=""; - } - else - _mt+=""; - - _subC=0; - if(_I[3]&&_I[24]) - _subC=1; - _timg=""; - _bimg=""; - if(_I[29]) - { - _imalgn=""; - if(_I[31]) - _imalgn="align="+_I[31]; - _imcspan=""; - if(_subC&&_imalgn&&_I[31]!="left") - _imcspan="colspan=2"; - _imgwd="width=1"; - if(_imalgn&&_I[31]!="left") - _imgwd=""; - _Iwid=""; - if(_I[37]) - _Iwid=" width="+_I[37]; - _Ihgt=""; - if(_I[38]) - _Ihgt=" height="+_I[38]; - _imgalt=""; - if(_I[58]) - _imgalt="alt=\""+_I[58]+"\""; - _timg=""; - if(_I[30]=="top") - _timg+=""; - if(_I[30]=="right") - { - _bimg=_timg; - _timg=""; - } - if(_I[30]=="bottom") - { - _bimg=""+_timg+""; - _timg=""; - } - } - _algn=""; - if(_M[8]) - _algn="align="+_M[8]; - if(_I[36]) - _algn="align="+_I[36]; - if(_M[8]) - _algn=" valign="+_M[8]; - if(_I[61]) - _algn=" valign="+_I[61]; - _iw=""; - _iheight=""; - _padd="padding:"+_I[11]+"px"; - _offbrd=""; - if(_I[9]) - _offbrd="border:"+_I[9]+";"; - if(_subC||_I[29]||(_M[4]&&horiz)) - { - _Limg=""; - _Rimg=""; - _itrs=""; - _itre=""; - if(_I[3]&&_I[24]) - { - _subIR=0; - if(_M[11]=="rtl") - _subIR=1; - _oif=""; - if(op7) - _oif=" onload=_oifx("+_el+") "; - _img=""; - _simgP=""; - if(_I[22]) - _simgP=";padding:"+_I[22]+"px"; - _imps="width=1"; - if(_I[23]) - { - _iA="width=1"; - _ivA=""; - _imP=_I[23].split(" "); - for(_ia=0;_ia<_imP.length;_ia++) - { - if(_imP[_ia]=="left") - _subIR=1; - if(_imP[_ia]=="right") - _subIR=0; - if(_imP[_ia]=="top"||_imP[_ia]=="bottom"||_imP[_ia]=="middle") - { - _ivA="valign="+_imP[_ia]; - if(_imP[_ia]=="bottom") - _subIR=0; - } - if(_imP[_ia]=="center") - { - _itrs=""; - _itre=""; - _iA="align=center width=100%"; - } - } - _imps=_iA+" "+_ivA; - } - _its=_itrs+""; - _ite=""+_itre; - if(_subIR) - { - _Limg=_its+_img+_ite; - } - else - { - _Rimg=_its+_img+_ite; - } - } - if(_M[4]) - _iw="width="+_M[4]; - if(_iw==""&&!_I[1]) - _iw="width=1"; - if(_I[55]) - _iw="width="+_I[55]; - if(!horiz) - _iw="width=100%"; - if(_M[18]) - { - _iheight="style=\"height:"+_M[18]+"px;\""; - } - if(_I[28]) - { - _iheight="style=\"height:"+_I[28]+"px;\""; - } - _mt+=""; - _mt+=""; - _mt+=""; - _mt+=_Limg; - _mt+=_timg; - _iw="width=100%"; - if(ie4||ns6) - _iw=""; - if(_I[1]) - { - _mt+=""; - } - else - { - _mt+=_link; - } - _mt+=_bimg; - _mt+=_Rimg; - _mt+=""; - _mt+="
    "+_link+" "+_I[1]+"
    "; - _mt+=""; - } - else - { - if(_M[18]) - _iheight="height:"+_M[18]+"px;"; - if(_I[28]) - _iheight="height:"+_I[28]+"px;"; - _iw=""; - if(_I[55]) - { - _iw="width="+_I[55]; - if(ns6) - _link="
    "+_link; - } - _mt+=""+_link+" "+_I[1]+""; - } - if((_M[0][_i]!=_M[0][_M[0].length-1])&&_I[27]>0) - { - _sepadd=""; - _brd=""; - _sbg=";background:"+_I[10]; - if(_I[71]) - _sbg=";background-image:url("+_I[71]+");"; - if(_I[27]) - { - if(horiz) - { - if(_I[49]) - { - _sepA="middle"; - if(_I[52]) - _sepA=_I[52]; - if(_I[51]) - _sepadd="style=\"padding:"+_I[51]+"px;\""; - _mt+="
    "; - } - else - { - if(_I[16]&&_I[17]) - { - _bwid=_I[27]/2; - if(_bwid<1) - _bwid=1; - _brdP=_bwid+"px solid "; - _brd+="border-right:"+_brdP+_I[16]+";"; - _brd+="border-left:"+_brdP+_I[17]+";"; - if(mac||sfri||(ns6&&!ns7)) - { - _mt+=""; - } - else - { - _mt+="
    "; - } - } - else - { - if(_I[51]) - _sepadd=""; - _mt+=_sepadd+"
    "+_sepadd; - } - } - } - else - { - if(_I[16]&&_I[17]) - { - _bwid=_I[27]/2; - if(_bwid<1) - _bwid=1; - _brdP=_bwid+"px solid "; - _brd="border-bottom:"+_brdP+_I[16]+";"; - _brd+="border-top:"+_brdP+_I[17]+";"; - if(mac||ns6||sfri) - _I[27]=0; - } - if(_I[51]) - _sepadd=""; - _sepW="100%"; - if(_I[50]) - _sepW=_I[50]; - _sepA="center"; - if(_I[52]) - _sepA=_I[52]; - if(!mac) - _sbg+=";overflow:hidden"; - _mt+=""+_sepadd+"
    "+_sepadd+""; - } - } - } -} - -function _fixMenu(_mnu) -{ - _gmt=gmobj("tbl"+_mnu); - _gm=gmobj("menu"+_mnu); - - if(op5) - _gm.style.pixelWidth=_gmt.style.pixelWidth+(_m[_mnu][12]*2)+(_m[_mnu][6][65]*2); - if((ie4)||_m[_mnu][14]=="relative") - _gm.style.width=_gmt.offsetWidth+"px"; - if(konq) - _gm.style.width=_gmt.offsetWidth; -} - -function getEVT(evt,_mnu) -{ - if(evt.target.tagName=="TD"){_egm=gmobj("menu"+_mnu); - gevent=evt.layerY-(evt.pageY-_d.body.offsetTop)+_egm.offsetTop;} -} - -function _drawMenu(_mnu,_begn) -{ - _mcnt++; - var _M=_m[_mnu]; - _top=""; - _left=""; - if(!_M[14]&&!_M[7]){_top="top:-999px"; - _left="left:-999px"}if(_M[2]!=_n){if(!isNaN(_M[2]))_top="top:"+_M[2]+"px"}if(_M[3]!=_n){if(!isNaN(_M[3]))_left="left:"+_M[3]+"px"}_mnuHeight=""; - if(_M[9]=="horizontal"||_M[9]==1){_M[9]=1; - horiz=1; - if(_M[18]){_mnuHeight="height="+_M[18]}}else{_M[9]=0; - horiz=0}_visi="hidden"; - _mt=""; - _nw=""; - _MS=_mi[_M[0][0]]; - _tablewidth=""; - if(_M[4]){_tablewidth="width="+_M[4]; - if(op7&&!IEDtD)_tablewidth="width="+(_M[4]-(_M[12]*2)-(_MS[65]*2))}else{if(!_M[17])_nw="nowrap"}_ofb=""; - if(_MS[7])_ofb="background:"+_MS[7]; - _brd=""; - _brdP=""; - _brdwid=""; - if(_MS[65]){_brdsty="solid"; - if(_MS[64])_brdsty=_MS[64]; - _brdcol="none"; - if(_MS[63])_brdcol=_MS[63]; - if(_MS[65])_brdwid=_MS[65]; - _brdP=_brdwid+"px "+_brdsty+" "; - _brd="border:"+_brdP+_brdcol+";"} - if(_MS[16]&&_MS[17]){_h3d=_MS[16];_l3d=_MS[17]; - if(_MS[70]){_h3d=_MS[17]; - _l3d=_MS[16]}_brdP=_brdwid+"px solid "; - _brd="border-bottom:"+_brdP+_h3d+";"; - _brd+="border-right:"+_brdP+_h3d+";"; - _brd+="border-top:"+_brdP+_l3d+";"; - _brd+="border-left:"+_brdP+_l3d+";"}_ns6ev=""; - if(_M[13]=="scroll"&&ns6&&!ns7)_ns6ev="onmousemove=\"getEVT(event,"+_mnu+")\""; - _bgimg=""; - if(_MS[73])_bgimg=";background-image:url("+_MS[73]+");"; - _posi="absolute"; - if(_M[14]){_posi=_M[14]; - if(_M[14]=="relative"){_posi=""; - if(!_M[4])_wid="width:1px;"; - _top=""; - _left=""}}_padd=""; - if(_M[12])_padd="padding:"+_M[12]+"px;"; - _wid=""; - _cls="mmenu"; - if(_MS[54])_cls=_MS[54]; - if(_posi)_posi="position:"+_posi; - _mnwid=""; - if(_M[17])_mnwid="width="+_M[17]; - if(_begn==1){if(!op6&&_mnwid&&!ns7)_wid=";width:"+_M[17]+";"; - _mt+=""; - - // For debugging the menu script - //document.all.debug.value += _mt; - - if(_begn==1) - _d.write(_mt); - else - return _mt; - - _M[22]=gmobj("menu"+_mnu); - if(_M[7]){if(ie55)drawiF(_mnu)}else{if(ie55&&_ifc<_mD)drawiF(_mnu); - _ifc++}if(_M[19]){_M[19]=_M[19].toString(); - _fs=_M[19].split(","); - if(!_fs[1])_fs[1]=50; - if(!_fs[2])_fs[2]=2; - _M[19]=_fs[0]; - followScroll(_mnu,_fs[1],_fs[2])}if(_mnu==_m.length-1){$CtI(_mst); - _mst=null; - _mst=setTimeout("_MScan()",150); - _getCurPath();} -} - -function _getCurPath() -{ - _cmp=new Array(); - if(_cip.length>0){for(_c=0; - _c<_cip.length; - _c++){_ci=_cip[_c]; - _mni=getParentItemByItem(_ci); - if(_mni==-1)_mni=_ci; - if(_mni+" "!="undefined "){while(_mni!=-1){_I=_mi[_mni]; - _setCPage(_I); - itemOff(_mni); - _cmp[_cmp.length]=_mni; - _mni=getParentItemByItem(_mni); - if(_mni+" "=="undefined ")_mni=-1;}}}} -} - -function _setPosition(_mnu) -{ - if(_m[_mnu][5]){_gm=gmobj("menu"+_mnu); - _gp=gpos(_gm); - _osl=0; - _omnu3=0; - if(isNaN(_m[_mnu][3])&&_m[_mnu][3].indexOf("offset=")==0){_omnu3=_m[_mnu][3]; - _m[_mnu][3]=_n; - _osl=_omnu3.substr(7,99); - _gm.leftOffset=_osl}_lft=_n; - if(!_m[_mnu][3]){if(_m[_mnu][5].indexOf("left")!=-1)_lft=0; - if(_m[_mnu][5].indexOf("center")!=-1)_lft=(_bW/2)-(_gp[3]/2); - if(_m[_mnu][5].indexOf("right")!=-1)_lft=_bW-_gp[3]; - if(_gm.leftOffset)_lft=_lft+parseInt(_gm.leftOffset)}_ost=0; - _omnu2=0; - if(isNaN(_m[_mnu][2])&&_m[_mnu][2].indexOf("offset=")==0){_omnu2=_m[_mnu][2]; - _m[_mnu][2]=_n; - _ost=_omnu2.substr(7,99); - _gm.topOffset=_ost}_tp=_n; - if(!_m[_mnu][2]>=0){_tp=_n; - if(_m[_mnu][5].indexOf("top")!=-1)_tp=0; - if(_m[_mnu][5].indexOf("middle")!=-1)_tp=(_bH/2)-(_gp[2]/2); - if(_m[_mnu][5].indexOf("bottom")!=-1)_tp=_bH-_gp[2]; - if(_gm.topOffset)_tp=_tp+parseInt(_gm.topOffset)}spos(_gm,_tp,_lft); - if(_m[_mnu][19])_m[_mnu][19]=_tp; - if(_m[_mnu][7])_SoT(_mnu,1); - _gm._tp=_tp;} -} - -function followScroll(_mnu,_cycles,_rate) -{ - if(!_startM){_M=_m[_mnu]; - _fogm=_M[22]; - _fgp=gpos(_fogm); - if(_sT>_M[2]-_M[19])_tt=_sT-(_sT-_M[19]); - else _tt=_M[2]-_sT; - if((_fgp[0]-_sT)!=_tt){diff=_sT+_tt; - if(diff-_fgp[0]<1)_rcor=_rate; - else _rcor=-_rate; - _nv=parseInt((diff-_rcor-_fgp[0])/_rate); - if(_nv!=0)diff=_fgp[0]+_nv; - spos(_fogm,diff); - if(_fgp._tp)_M[19]=_fgp._tp; - if(ie55){_fogm=gmobj("ifM"+_mnu); - if(_fogm)spos(_fogm,diff)}}}_fS=setTimeout("followScroll(\""+_mnu+"\","+_cycles+","+_rate+")",_cycles); -} - -function _MScan() -{ - _getDims(); - if(_bH!=_oldbH||_bW!=_oldbW){for(_a=0; - _a<_m.length; - _a++){if(_m[_a][7]){if(_startM&&(ie4||_m[_a][14]=="relative")){_fixMenu(_a)}menuDisplay(_a,1)}}for(_a=0; - _a<_m.length; - _a++){if(_m[_a][5]){_setPosition(_a)}}}if(_startM)_mnuD=0; - _startM=0; - _oldbH=_bH; - _oldbW=_bW; - if(!op&&_d.all&&_d.readyState!="complete"){_oldbH=0; - _oldbW=0}if(op){}_mst=setTimeout("_MScan()",150); -} - -function drawiF(_mnu) -{ - _gm=gmobj("menu"+_mnu); - _gp=gpos(_gm); - _ssrc=""; - if(location.protocol=="https:")_ssrc="src=/blank.html"; - if(_m[_mnu][7]){_mnuV="ifM"+_mnu}else{_mnuV="iF"+_mnuD; - _mnuD++}if(!window._CFix)_d.write("")}function _SoT(_mnu,_on){if(_m[_mnu][14]=="relative")return; - if(ns6)return; - if(ie55){if(_on){if(!_m[_mnu][7]){_iF=gmobj("iF"+_mnuD); - if(!_iF){if(_d.readyState!="complete")return; - _iF=_d.createElement("iframe"); - if(location.protocol=="https:")_iF.src="/blank.html"; - _iF.id="iF"+_mnuD; - _iF.style.filter="Alpha(Opacity=0)"; - _iF.style.position="absolute"; - _iF.style.className="mmenu"; - _d.body.appendChild(_iF)}}else{_iF=gmobj("ifM"+_mnu)}_gp=gpos(_m[_mnu][22]); - if(_iF){spos(_iF,_gp[0],_gp[1],_gp[2],_gp[3]); - _iF.style.visibility="visible"; - _iF.style.zIndex=1}}else{_gm=gmobj("iF"+(_mnuD-1)); - if(_gm)_gm.style.visibility="hidden";}} -} diff --git a/src/js/menu_misc.js b/src/js/menu_misc.js deleted file mode 100644 index 2bfcb28..0000000 --- a/src/js/menu_misc.js +++ /dev/null @@ -1,285 +0,0 @@ -/* -Javscript menu -Misc stuff -*/ - -_mD=2; -_d=document; -_n=navigator; -_nv=$tL(_n.appVersion); -_nu=$tL(_n.userAgent); -_ps=parseInt(_n.productSub); -_f=false; -_t=true; -_n=null; -_wp=window.createPopup; -ie=(_d.all)?_t:_f; -ie4=(!_d.getElementById&&ie)?_t:_f; -ie5=(!ie4&&ie&&!_wp)?_t:_f; -ie55=(!ie4&&ie&&_wp)?_t:_f; -ns6=(_nu.indexOf("gecko")!=-1)?_t:_f; -konq=(_nu.indexOf("konqueror")!=-1)?_t:_f; -sfri=(_nu.indexOf("safari")!=-1)?_t:_f; -if(konq||sfri){_ps=0; -ns6=0}ns4=(_d.layers)?_t:_f; -ns61=(_ps>=20010726)?_t:_f; -ns7=(_ps>=20020823)?_t:_f; -op=(window.opera)?_t:_f; -op5=(_nu.indexOf("opera 5")!=-1)?_t:_f; -op6=(_nu.indexOf("opera 6")!=-1)?_t:_f; -op7=(_nu.indexOf("opera 7")!=-1||_nu.indexOf("opera/7")!=-1)?_t:_f; -mac=(_nv.indexOf("mac")!=-1)?_t:_f; -mac45=(_nv.indexOf("msie 4.5")!=-1)?_t:_f; -mac50=(mac&&_nv.indexOf("msie 5.0")!=-1)?_t:_f; -if(ns6||ns4||op||sfri)mac=_f; -ns60=_f; -if(ns6&&!ns61)ns60=_t; -IEDtD=0; -if(!op&&(_d.all&&_d.compatMode=="CSS1Compat")||(mac&&_d.doctype&&_d.doctype.name.indexOf(".dtd")!=-1))IEDtD=1; -if(op7)op=_f; -if(op)ie55=_f; -_st=0; -_en=0; -$=" "; -_m=new Array(); -_mi=new Array(); -_sm=new Array(); -_tsm=new Array(); -_cip=new Array(); -_mn=-1; -_el=0; -_ael=0; -_Bel=0; -_bl=0; -_Omenu=0; -_MT=setTimeout("",0); -_oMT=setTimeout("",0); -_cMT=setTimeout("",0); -_scrmt=setTimeout("",0); -_mst=setTimeout("",0); -_zi=999; -_c=1; -_mt=""; -_oldel=-1; -_sH=0; -_sW=0; -_bH=500; -_oldbH=0; -_bW=0; -_oldbW=0; -_cD=0; -_ofMT=0; -_startM=1; -_sT=0; -_sL=0; -_mcnt=0; -_mnuD=0; -_itemRef=-1; -inopenmode=0; - -_$S={menu:0,text:1,url:2,showmenu:3,status:4,onbgcolor:5,oncolor:6,offbgcolor:7,offcolor:8,offborder:9,separatorcolor:10,padding:11,fontsize:12,fontstyle:13,fontweight:14,fontfamily:15,high3dcolor:16,low3dcolor:17,pagecolor:18,pagebgcolor:19,headercolor:20,headerbgcolor:21,subimagepadding:22,subimageposition:23,subimage:24,onborder:25,ondecoration:26,separatorsize:27,itemheight:28,image:29,imageposition:30,imagealign:31,overimage:32,decoration:33,type:34,target:35,align:36,imageheight:37,imagewidth:38,openonclick:39,closeonclick:40,keepalive:41,onfunction:42,offfunction:43,onbold:44,onitalic:45,bgimage:46,overbgimage:47,onsubimage:48,separatorheight:49,separatorwidth:50,separatorpadding:51,separatoralign:52,onclass:53,offclass:54,itemwidth:55,pageimage:56,targetfeatures:57,imagealt:58,pointer:59,imagepadding:60,valign:61,clickfunction:62,bordercolor:63,borderstyle:64,borderwidth:65,overfilter:66,outfilter:67,margin:68,pagebgimage:69,swap3d:70,separatorimage:71,pageclass:72,menubgimage:73}; -$So=""; -_$M={items:0,name:1,top:2,left:3,itemwidth:4,screenposition:5,style:6,alwaysvisible:7,align:8,orientation:9,keepalive:10,openstyle:11,margin:12,overflow:13,position:14,overfilter:15,outfilter:16,menuwidth:17,itemheight:18,followscroll:19,menualign:20,mm_callItem:21,mm_obj_ref:22}; -_pru=""; -_c=0; -menuname.prototype.SbMnu=ami; -menuname.prototype.insertItem=_iI; - -function M_hideLayer(){} -function opentree(){} - -function chop(_ar,_pos) -{ - var _tar=new Array(); - for(_a=0;_a<_ar.length;_a++) - { - if(_a!=_pos) - { - _tar[_tar.length]=_ar[_a]; - } - } - return _tar; -} - -function remove(_ar,_dta) -{ - var _tar=new Array(); - for(_a=0;_a<_ar.length;_a++) - { - if(_ar[_a]!=_dta) - {_tar[_tar.length]=_ar[_a]} - } - - return _tar; -} - -function copyOf(_w){for(_cO in _w) -{ - this[_cO]=_w[_cO]} -} - -function $tL($S) -{ - return $S.toLowerCase(); -} - -function MakeMenus() -{ - for(_a=_mcnt;_a<_m.length;_a++) - {_drawMenu(_a,1)} -} - -function MenuStyle() -{ - for($i in _$S) - this[$i]=_n; -} - -function menuname(name) -{ - for($i in _$M) - this[$i]=_n; - - this.name=$tL(name);_c=1; - _mn++; - this.menunumber=_mn; -} - -function _incItem(_it) -{ - _mi[_bl]=new Array(); - - for($i in _x[6]) - _mi[_bl][_$S[$i]]=_x[6][$i]; - - _mi[_bl][0]=_mn; - _it=_it.split(";"); - - for(_a=0;_a<_it.length;_a++) - { - _sp=_it[_a].indexOf("`"); - if(_sp!=-1) - { - _tI=_it[_a]; - for(_b=_a;_b<_it.length;_b++) - { - _tI+=";"+_it[_b+1]; - _a++; - if(_it[_b+1].indexOf("`")!=-1) - _b=_it.length - } - _it[_a]=_tI.replace(/`/g,"") - } - _sp=_it[_a].indexOf("="); - if(_sp==-1) - { - if(_it[_a]) - _si=_si+";"+_it[_a]; - } - else - { - _si=_it[_a].slice(_sp+1); - _w=_it[_a].slice(0,_sp); - if(_w=="showmenu") - _si=$tL(_si) - } - - if(_it[_a]) - { - _mi[_bl][_$S[_w]]=_si; - } - } - - _m[_mn][0][_c-2]=_bl; - _c++; - _bl++; - _mil=1; - - if(_m[_mn][7]&&_c==3) - { - $c=0; - for($i in _$S) - { - if($c==2) - $T2=";"+$i; - if($c==1) - $T1=$i+"="; - $c++ - } - $1=eval("$tL(String.fromCharCode(95,80,82,85))"); - $2=eval($1).split($); - } - _mil=2; -} - -function _iI(txt,_pos) -{ - _oStyle=_m[_mn][6]; - _m[_mn][6]=this.style; - this.SbMnu(txt); - _mil=_mi.length; - _M=_m[this.menunumber]; - _nmi=new Array(); - if(_pos>=_M[0].length)_pos=_M[0].length; - if(!_M[0][_pos])_M[0][_pos]=_M[0][_M[0].length-1]+1; - _inum=_M[0][_pos]; - _cnt=0; - for(_a=0;_a<_mil;_a++) - { - if(_inum==_a) - { - _nmi[_cnt]=_mi[_mi.length-1]; - _nmi[_cnt][0]=this.menunumber; - _M[0][_M[0].length]=_cnt; - _cnt++ - } - _nmi[_cnt]=_mi[_a]; - _cnt++ - } - - _mi=_nmi; - _tpos=0; - _omnu=-1; - - for(_a=0;_a<_mil;_a++) - { - _mnu=_mi[_a][0]; - if(_mnu!=_omnu) - { - _m[_mnu][0]=new Array(); - _tpos=0; - } - _m[_mnu][0][_tpos]=_a; - _tpos++; - _omnu=_mnu; - } - _m[_mn][6]=_oStyle; -} - -function ami(txt) -{ - _t=this; - if(_c==1) - { - _c++; - _m[_mn]=new Array(); - _x=_m[_mn]; - for($i in _t) - _x[_$M[$i]]=_t[$i]; - - _x[21]=-1; - _x[0]=new Array(); - if(!_x[12])_x[12]=0; - _MS=_m[_mn][6]; - _MN=_m[_mn]; - if(!_MN[15]) - _MN[15]=_MS.overfilter; - if(!_MN[16]) - _MN[16]=_MS.outfilter; - if(!_MN[12]) - _MN[12]=_MS.margin; - if(!_MS[65]) - _MS[65]=0; - } - _incItem(txt); -} \ No newline at end of file diff --git a/src/js/menu_ns4.js b/src/js/menu_ns4.js deleted file mode 100644 index 02aa7b7..0000000 --- a/src/js/menu_ns4.js +++ /dev/null @@ -1,678 +0,0 @@ -/* -Javscript menu -NS4 stuff -*/ - -_amt=""; -_MTF=0; -_onTS=0; -var _cel=-1; - -function gmobj(mtxt) -{ - if(_d.layers[mtxt]) - return _d.layers[mtxt]; - re=/\d*\d/; - fnd=re.exec(mtxt); - if(_d.layers["menu"+_mi[fnd][0]]) - { - return _d.layers["menu"+_mi[fnd][0]].document.layers["il"+fnd].document.layers[mtxt]; - } - else - { - return document.layers["il"+fnd].document.layers[mtxt]; - } -} - -function spos(gm,t_,l_,h_,w_) -{ - if(t_!=null) - gm.top=t_; - if(l_!=null) - gm.left=l_; - if(h_!=null) - gm.height=h_; - if(w_!=null) - gm.width=w_; -} - -function gpos(gm) -{ - var gpa=new Array(); - gpa[0]=gm.pageY; - gpa[1]=gm.pageX; - gpa[2]=gm.clip.height; - gpa[3]=gm.clip.width; - return(gpa) -} - -function _lc(_dummy) -{ - if(window.retainClickValue) - inopenmode=1; - - _i=nshl; - if(_mi[_i][62]) - eval(_mi[_i][62]); - if(_i>-1) - { - if(_mi[_i][2]) - { - location.href=_mi[_i][2]; - } - else - { - if(_mi[_i][39]||_mi[_i][40]) - { - _nullLink(_i); - } - } -} -} - -function _nullLink(_i) -{if(_mi[_i][3]){_oldMC=_mi[_i][39]; -_mi[_i][39]=0; -_oldMD=_menuOpenDelay; -_menuOpenDelay=0; -_gm=gmobj("menu"+getMenuByName(_mi[_i][3])); -if(_gm.visibility=="show"&&_mi[_i][40]){menuDisplay(getMenuByName(_mi[_i][3]),0); -itemOn(_i)}else{_popi(_i)}_menuOpenDelay=_oldMD; -_mi[_i][39]=_oldMC} -} - -function itemOn(_i) -{clearTimeout(_scrmt); -if(_mi[_i][34]=="header"||_mi[_i][34]=="form")return; -_gm=gmobj("oel"+_i); -_gm.visibility="show"; -if(_mi[_i][42])eval(_mi[_i][42]) -} - -function itemOff(_i) -{if(_i>-1){_gm=gmobj("oel"+_i); -_gm.visibility="hide"; -if(_mi[_i][43])eval(_mi[_i][43])}}_NS4S=new Array(); -function drawItem(_i){_Tmt=""; -_Dmnu=_mi[_i][0]; -var _M=_m[_Dmnu]; -var _mE=_mi[_i]; -if(!_NS4S[_i]){if(!_mi[_i][33])_mi[_i][33]="none"; -if(!_mi[_i][26])_mi[_i][26]="none"; -if(!_mi[_i][14])_mi[_i][14]="normal"; -_st=".item"+_i+"{"; -if(_mi[_i][33])_st+="textDecoration:"+_mi[_i][33]+";"; -if(_mi[_i][15])_st+="fontFamily:"+_mi[_i][15]+";"; -if(_mi[_i][14])_st+="fontWeight:"+_mi[_i][14]+";"; -if(_mi[_i][12])_st+="fontSize:"+_mi[_i][12]+";"; -_st+="}"; -_st+=".oitem"+_i+"{"; -if(_mi[_i][15])_st+="fontFamily:"+_mi[_i][15]+";"; -if(_mi[_i][14])_st+="fontWeight:"+_mi[_i][14]+";"; -if(_mi[_i][33])_st+="textDecoration:"+_mi[_i][33]+";"; -if(_mi[_i][44])_st+="fontWeight:bold;"; -if(_mi[_i][45])_st+="fontStyle:italic;"; -if(_mi[_i][12])_st+="fontSize:"+_mi[_i][12]+";"; -if(_mi[_i][26])_st+="textDecoration:"+_mi[_i][26]+";"; -_st+="}"; -_d.write(""); -_NS4S[_i]=_i}_lnk="javascript:_nullLink("+_i+");"; -if(_mi[_i][2])_lnk="javascript:_lc("+_i+")"; -_wid=""; -if(_M[4])_wid="width="+_M[4]; -if(_mi[_i][55])_wid="width="+_mi[_i][55]; -_hgt=""; -if(_M[18]){_hgt="height="+_M[18]}if(_mi[_i][28]){_hgt="height="+_mi[_i][28]}_pad="0"; -if(_mE[11])_pad=_mE[11]; -if(_mi[_i][34]=="header"){if(_mi[_i][20])_mi[_i][8]=_mi[_i][20]; -if(_mi[_i][20])_mi[_i][7]=_mi[_i][21]}_bgc=""; -if(_mi[_i][7]=="transparent")_mi[_i][7]=_n; -if(_mi[_i][7])_bgc="bgcolor="+_mi[_i][7]; -_fgc=""; -if(_mi[_i][8])_fgc=""; -_bgbc=""; -if(_mi[_i][5])_bgbc="bgcolor="+_mi[_i][5]; -_fgbc=""; -if(_mi[_i][6])_fgbc=""; -_algn=""; -if(_M[8])_algn=" align="+_M[8]; -if(_mi[_i][36])_algn=" align="+_mi[_i][36]; -if(_mi[_i][61])_algn=" valign="+_mi[_i][61]; -_nw=""; -if(!_M[4]&&!_mi[_i][55])_nw=" nowrap "; -_iMS=""; -_iME=""; -if(_lnk){_iMS=""; -_iME=""}_Lsimg=""; -_Rsimg=""; -_LsimgO=""; -_RsimgO=""; -_itrs=""; -_itre=""; -if(_mi[_i][3]&&_mi[_i][24]){_subIR=0; -if(_M[11]=="rtl")_subIR=1; -_img=_iMS+""+_iME; -_oimg=_img; -if(_mi[_i][48])_oimg=_iMS+""+_iME; -_simgP=""; -if(_mi[_i][22])_simgP=_mi[_i][22]; -_imps=""; -if(_mi[_i][23]){_iA=""; -_ivA=""; -_imP=_mi[_i][23].split(" "); -for(_ia=0; -_ia<_imP.length; -_ia++){if(_imP[_ia]=="left")_subIR=1; -if(_imP[_ia]=="right")_subIR=0; -if(_imP[_ia]=="top"||_imP[_ia]=="bottom"||_imP[_ia]=="middle"){_ivA="valign="+_imP[_ia]; -if(_imP[_ia]=="top")_subIR=1; -if(_imP[_ia]=="bottom")_subIR=0}if(_imP[_ia]=="center"){_itrs=""; -_itre=""; -_iA="align=center"}}_imps=_iA+" "+_ivA}_its=_itrs+"
    "; -_ite="
    "+_itre; -if(_subIR)_Lsimg=_its+_img+_ite; -else _Rsimg=_its+_img+_ite; -if(_subIR)_LsimgO=_its+_oimg+_ite; -else _RsimgO=_its+_oimg+_ite}_Limg=""; -_Rimg=""; -_LimgO=""; -_RimgO=""; -if(_mi[_i][29]){_iA=""; -_ivA=""; -_imps=""; -_Iwid=""; -if(_mi[_i][37])_Iwid=" width="+_mi[_i][37]; -_Ihgt=""; -if(_mi[_i][38])_Ihgt=" height="+_mi[_i][38]; -_img=_iMS+""+_iME; -_oimg=_img; -if(_mi[_i][32])_oimg=_iMS+""+_iME; -if(!_mi[_i][30])_mi[_i][30]="left"; -_imP=_mi[_i][30].split(" "); -for(_ia=0; -_ia<_imP.length; -_ia++){if(_imP[_ia]=="left")_subIR=1; -if(_imP[_ia]=="right")_subIR=0; -if(_imP[_ia]=="top"||_imP[_ia]=="bottom"||_imP[_ia]=="middle"){_ivA="valign="+_imP[_ia]; -if(_mi[_i][3])_ivA+=" colspan=2"; -if(_imP[_ia]=="top")_subIR=1; -if(_imP[_ia]=="bottom")_subIR=0}if(_imP[_ia]=="center"){_itrs=""; -_itre=""; -_iA="align=center"}}_imps=_iA+" "+_ivA; -_its=_itrs+"
    "; -_ite="
    "+_itre; -if(!_mi[_i][1]){_its=""; -_ite=""}if(_subIR)_Limg=_its+_img+_ite; -else _Rimg=_its+_img+_ite; -if(_subIR)_LimgO=_its+_oimg+_ite; -else _RimgO=_its+_oimg+_ite}if(!_M[9]){_Tmt+=""}_Tmt+=""; -_Tmt+=""; -_txt=""; -if(_mi[_i][1])_txt=_mi[_i][1]; -_acT="onmouseover=\"_popi("+_i+");clearTimeout(_MTF);_MTF=setTimeout('close_el("+_i+")',200);\";drag_drop('menu"+_Dmnu+"');"; -if(_mi[_i][34]=="dragable"){}if(_mi[_i][34]=="header")_acT=""; -_Tmt+=""; -_Tmt+="
    "; -_Tmt+=""; -_Tmt+=_Limg; -_Tmt+=_Lsimg; -if(_txt){_Tmt+=""}_Tmt+=_Rimg; -_Tmt+=_Rsimg; -_Tmt+="
    "; -_Tmt+=""; -_Tmt+=_fgc+_txt; -_Tmt+=""; -_Tmt+="
    "; -_Tmt+="
    "; -_Tmt+=""; -_Tmt+="
    "; -_Tmt+=""; -_Tmt+=_LimgO; -_Tmt+=_LsimgO; -if(_txt){_Tmt+=""}_Tmt+=_RimgO; -_Tmt+=_RsimgO; -_Tmt+="
    "; -_Tmt+=""; -_Tmt+=_fgbc+_txt; -_Tmt+=""; -_Tmt+="
    "; -_Tmt+="
    "; -_Tmt+="
    "; -_Tmt+=""; -_hgt=""; -if(_M[18]){_hgt="height="+(_M[18]+6); -_hgt="height=20"}_spd=""; -if(_mi[_i][51])_spd=_mi[_i][51]; -_sal="align=center"; -if(_mi[_i][52])_sal="align="+_mi[_i][52]; -_sbg=""; -if(_mi[_i][71])_sbg="background="+_mi[_i][71]; -if(!_M[9]){_Tmt+=""; -if((_i!=_M[0][_M[0].length-1])&&_mi[_i][27]>0){_swid="100%"; -if(_mi[_i][50])_swid=_mi[_i][50]; -if(_spd)_Tmt+=""; -_Tmt+=""; -if(_mi[_i][16]&&_mi[_i][17]){_bwid=_mi[_i][27]/2; -if(_bwid<1)_bwid=1; -_Tmt+=""; -_Tmt+=""}else{_Tmt+=""}_Tmt+="
    "; -_Tmt+="
    "; -_Tmt+="
    "; -_Tmt+="
    "; -if(_spd)_Tmt+=""}}else{if((_i!=_M[0][_M[0].length-1])&&_mi[_i][27]>0){_hgt="height=100%"; -if(_mi[_i][16]&&_mi[_i][17]){_bwid=_mi[_i][27]/2; -if(_bwid<1)_bwid=1; -_Tmt+=""; -_Tmt+=""}else{if(_spd)_Tmt+=""; -_Tmt+=""; -if(_spd)_Tmt+=""}}}return _Tmt -} - -function csto(_mnu) -{_onTS=0; -clearTimeout(_scrmt); -clearTimeout(_oMT); -_MT=setTimeout("closeAllMenus()",_menuCloseDelay) -} - -function followScroll(_mnu,_cycles,_rate) -{if(!_startM){_M=_m[_mnu]; -_fogm=_M[22]; -_fgp=gpos(_fogm); -if(_sT>_M[2]-_M[19])_tt=_sT-(_sT-_M[19]); -else _tt=_M[2]-_sT; -_tt+=_M[6][65]; -if((_fgp[0]-_sT)!=_tt){diff=_sT+_tt; -if(diff-_fgp[0]<1)_rcor=_rate; -else _rcor=-_rate; -_nv=parseInt((diff-_rcor-_fgp[0])/_rate); -if(_nv!=0)diff=_fgp[0]+_nv; -spos(_fogm,diff); -if(_fgp._tp)_M[19]=_fgp._tp; -_fgp=gpos(_fogm); -spos(gmobj("bord"+_mnu),_fgp[0]-_m[_mnu][6][65])}}_fS=setTimeout("followScroll(\""+_mnu+"\","+_cycles+","+_rate+")",_cycles) -} - -function _drawMenu(_mnu) -{_mt=""; -_mcnt++; -var _M=_m[_mnu]; -_ms=_m[_mnu][6]; -if(_M[9]=="horizontal")_M[9]=1; -else _M[9]=0; -_visi=""; -if(!_M[7])_visi="visibility=hide"; -_top="top=0"; -if(_M[2])_top="top="+_M[2]; -_left="left=0"; -if(_M[3])_left="left="+_M[3]; -if(_M[9]){_oldBel=_Bel; -_d.write(""); -for(_b=0; -_b<_M[0].length; -_b++){_d.write(drawItem(_Bel)); -_Bel++}_d.write("
    "); -_Bel=_oldBel; -_gm=gmobj("HT"+_mnu); -_M[18]=_gm.clip.height-6}_bImg=""; -if(_M[6][46])_bImg="background="+_M[6][46]; -if(_M[14]!="relative")_mt+="";_bgc="";if(_m[_mnu][6].offbgcolor=="transparent")_m[_mnu][6].offbgcolor=_n;if(_m[_mnu][6].offbgcolor)_bgc="bgcolor="+_m[_mnu][6].offbgcolor;_mrg=0;if(_M[12])_mrg=_M[12];_mt+="";_mt+="";_mt+="
    ";_mt+="";for(_b=0;_b<_M[0].length;_b++){_mt+=drawItem(_Bel);_Bel++}_mt+="
    ";_mt+="
    ";if(_M[14]!="relative")_mt+="
    ";_amt+=_mt;_d.write(_mt);_M[22]=gmobj("menu"+_mnu);if(_M[19]){_M[19]=_M[19].toString();_fs=_M[19].split(",");if(!_fs[1])_fs[1]=20;if(!_fs[2])_fs[2]=10;_M[19]=_fs[0];followScroll(_mnu,_fs[1],_fs[2])}if(_M[14]!="relative"){_st="";_brdsty="solid";if(_M[6].borderstyle)_brdsty=_M[6].borderstyle;if(_M[6][64])_brdsty=_M[6][64];_brdcol="#000000";if(_M[6].bordercolor)_brdcol=_M[6].bordercolor;if(_M[6][63])_brdcol=_M[6][63];_brdwid="";if(_M[6].borderwidth)_brdwid=_M[6].borderwidth;if(_M[6][65])_brdwid=_M[6][65];_M[6][65]=_brdwid;_st=".menu"+_mnu+"{";_st+="borderStyle:"+_brdsty+";"; -_st+="borderColor:"+_brdcol+";"; -_st+="borderWidth:"+_brdwid+";"; -if(_ms.fontsize)_st+="fontSize:"+2+";"; -_st+="}"; -_d.write(""); -_gm=gmobj("menu"+_mnu); -_d.write("
    "); -if(_M[7]){_gm=gmobj("menu"+_mnu); -_gm.zIndex=999; -_gp=gpos(_gm); -spos(_gm,_gp[0]+_M[6][65],_gp[1]+_M[6][65],_gp[2],_gp[3]); -_gmb=gmobj("bord"+_mnu); -_gmb.zIndex=0; -spos(_gmb,_gp[0],_gp[1],_gp[2],_gp[3]); -_gmb.visibility="show"}}else{}if(_m[_mnu][13]=="scroll"){_gm=gmobj("menu"+_mnu); -_gm.fullHeight=_gm.clip.height; -_scs="; -this.bgColor='"+_m[_mnu][6].onbgcolor+"'\" onmouseout=\"csto("+_mnu+"); -this.bgColor='"+_m[_mnu][6].offbgcolor+"'\""; -_scs+=" visibility=hide "+_bgc+" class=menu"+_mnu+">
    "; -_sce="
    "; -_d.write(""+_sce); -_d.write(""+_sce); -_ts=gmobj("tscroll"+_mnu); -_gm.tsHeight=_ts.clip.height; -_ts=gmobj("bscroll"+_mnu); -_gm.bsHeight=_ts.clip.height} -} - -function getMenuByItem(_gel) -{_gel=_mi[_gel][0]; -if(_m[_gel][7])_gel=-1; -return _gel -} - -function getParentMenuByItem(_gel) -{_tm=getMenuByItem(_gel); -if(_tm==-1)return-1; -for(_x=0; -_x<_mi.length; -_x++){if(_mi[_x][3]==_m[_tm][1]){return _mi[_x][0]}}return-1 -} - -function getParentItemByItem(_gel) -{_tm=getMenuByItem(_gel); -if(_tm==-1)return-1; -for(_x=0; -_x<_mi.length; -_x++){if(_mi[_x][3]==_m[_tm][1]){return _x}}return-1 -} - -function _setPath(_mpi) -{if(_mpi>-1){_ci=_m[_mpi][21]; -while(_ci>-1){itemOn(_ci); -_ci=_m[_mi[_ci][0]][21]}} -} - -function _back2par(_i) -{if(_oldel>-1){if(_i==_m[_mi[_oldel][0]][21]){_popi(_i)}} -} - -function closeMenusByArray(_ar) -{for(_a=0; -_a<_ar.length; -_a++){menuDisplay(_ar[_a],0)} -} - -function cm() -{_tar=getMenusToClose(); -closeMenusByArray(_tar); -for(_b=0; -_b<_tar.length; -_b++){if(_tar[_b]!=_mnu)_sm=remove(_sm,_tar[_b])} -} - -function getMenusToClose() -{_st=-1; -_en=_sm.length; -_mm=_iP; -if(_iP==-1){if(_sm[0]!=_masterMenu)return _sm; -_mm=_masterMenu}for(_b=0; -_b<_sm.length; -_b++){if(_sm[_b]==_mm)_st=_b+1; -if(_sm[_b]==_mnu)_en=_b}if(_st>-1&&_en>-1){_tsm=_sm.slice(_st,_en)}return _tsm -} - -function getMenuByName(_mname) -{_mname=$tL(_mname); -for(_gma=0; -_gma<_m.length; -_gma++){if(_mname==_m[_gma][1]){return _gma}}return-1 -} - -function clearELs(_i) -{_mnu=_mi[_i][0]; -for(_q=0; -_q<_m[_mnu][0].length; -_q++){gmobj("oel"+_m[_mnu][0][_q]).visibility="hide"} -} - -function menuDisplay(_mnu,_show) -{_gm=gmobj("menu"+_mnu); -_gmb=gmobj("bord"+_mnu); -M_hideLayer(_mnu,_show); -for(_q=0; -_q<_m[_mnu][0].length; -_q++){gmobj("oel"+_m[_mnu][0][_q]).visibility="hide"}if(_show){_gm.zIndex=_zi; -_gm.visibility="show"; -_gmb.top=_gm.pageY-_m[_mnu][6][65]; -_gmb.left=_gm.pageX-_m[_mnu][6][65]; -_gmb.zIndex=_zi-1; -_gmb.visibility="show"; -if(_el>-1)_m[_mnu][21]=_el; -if(_m[_mnu][13]=="scroll"){if((_gm.clip.height>_bH)||_gm.nsDoScroll){_gi=gmobj("el"+_el); -_tsm=gmobj("tscroll"+_mnu); -_bsm=gmobj("bscroll"+_mnu); -if(!_gm.scrollTop)_gm.top=_gm.top+_tsm.clip.height-1; -else _gm.top=_gm.scrollTop; -_gm.clip.height=_bH-(_gi.pageY+_gi.clip.height)-19; -_gmb.clip.height=_gm.clip.height; -_tsm.top=_gmb.top; -_tsm.left=_gmb.left; -_tsm.zIndex=_zi+1; -_bsm.left=_gmb.left; -_bsm.top=(_gmb.pageY+_gmb.clip.height)-_tsm.clip.height+_gm.tsHeight; -_tsm.zIndex=_zi+1; -_tsm.visibility="show"; -_bsm.zIndex=_zi+1; -_bsm.visibility="show"; -_gm.nsDoScroll=1}}}else{if(!(_m[_mnu][7])){_gm.visibility="hide"; -_gmb.visibility="hide"; -if(_m[_mnu][13]=="scroll"){_tsm=gmobj("tscroll"+_mnu); -_tsm.visibility="hide"; -_tsm=gmobj("bscroll"+_mnu); -_tsm.visibility="hide"}}} -} - -function forceCloseAllMenus() -{_cmo=gmobj("menu"+_mi[_cel][0]); -if(!_cmo)_cmo=gmobj("oel"+_cel); -for(_a=0; -_a<_m.length; -_a++){if(!_m[_a][7]&&!_m[_a][10])menuDisplay(_a,0)}_zi=999; -_el=-1 -} - -function closeAllMenus() -{_cmo=gmobj("menu"+_mi[_cel][0]); -if(!_cmo)_cmo=gmobj("oel"+_cel); -if(!_onTS&&_cmo&&(MouseX>(_cmo.pageX+_cmo.clip.width)||MouseY>(_cmo.pageY+_cmo.clip.height)||MouseX<_cmo.pageX||MouseY<_cmo.pageY)){inopenmode=0; -for(_ca=0; -_ca<_m.length; -_ca++){if(!_m[_ca][7]&&!_m[_ca][10])menuDisplay(_ca,0); -if(_m[_ca][21]>-1){itemOff(_m[_ca][21]); -_m[_ca][21]=-1}}_zi=999; -_el=-1} -} - -function close_menu() -{if(_el==-1)_MT=setTimeout("closeAllMenus()",_menuCloseDelay) -} - -function close_el(_i) -{if(_mi[_i][43])eval(_mi[_i][43]); -clearELs(_i); -window.status=""; -clearTimeout(_oMT); -_MT=setTimeout("closeAllMenus()",_menuCloseDelay); -_el=-1; -_oldel=_i -} - -function getParentMenuByItem(_gel) -{_gel=_mi[_gel][0]; -if(_m[_gel][7])_gel=-1; -return _gel -} - -function getParentItemByItem(_gel) -{_par=getParentMenuByItem(_gel); -for(_a=0; -_a<_m[_par][0].length; -_a++){if(_gel==_m[_par][0][_a]){return _m[_par][0]}}return false -} - -function getParentsByItem(_gmi) -{ -} - -function lc(_i) -{if(_mi[_i]=="disabled")return; -location.href=_mi[_i][2] -} - -function _is(_mnu,_SCRam) -{_onTS=1; -_cel=_m[_mnu][0][0]; -clearTimeout(_MT); -clearTimeout(_scrmt); -_doScroll(_mnu,_SCRam); -_scrmt=setTimeout("_is("+_mnu+","+_SCRam+")",_scrollDelay) -} - -function _doScroll(_mnu,_SCRam){gm=gmobj("menu"+_mnu); -if(_SCRam<0&&((gm.clip.top+gm.clip.height)>gm.fullHeight+gm.tsHeight+_SCRam))return; -if(_SCRam>0&&gm.clip.top<_SCRam)return; -gm.top=gm.top+_SCRam; -gm.scrollTop=gm.top; -gm.clip.top=gm.clip.top-_SCRam; -gm.clip.height=gm.clip.height-_SCRam -} - -function set_status(_i) -{if(_mi[_i][4]!=null){status=_mi[_i][4]}else{if(_mi[_i][2])status=_mi[_i][2]; -else status=""} -} - -function getOffsetValue(_ofs) -{_ofsv=0; -if(isNaN(_ofs)&&_ofs.indexOf("offset=")==0){_ofsv=parseInt(_ofs.substr(7,99))}return _ofsv -} - -function popup() -{_sm=new Array; -_arg=arguments; -clearTimeout(_MT); -clearTimeout(_oMT); -if(_cel>-1)forceCloseAllMenus(); -if(_arg[0]){_ofMT=0; -_mnu=getMenuByName(_arg[0]); -_cel=_m[_mnu][0][0]; -_tos=0; -if(_arg[2])_tos=_arg[2]; -_los=0; -if(_arg[3])_los=_arg[3]; -_sm[_sm.length]=_mnu; -if(_arg[1]){_gm=gmobj("menu"+_mnu); -_gp=gpos(_gm); -if(_arg[1]==1){if(MouseY+_gp[2]>(_bH)+_sT)_tos=-(MouseY+_gp[2]-_bH)+_sT; -if(MouseX+_gp[3]>(_bW)+_sL)_los=-(MouseX+_gp[3]-_bW)+_sL; -if(_m[_mnu][2]){if(isNaN(_m[_mnu][2]))_tos=getOffsetValue(_m[_mnu][2]); -else{_tos=_m[_mnu][2]; -MouseY=0}}if(_m[_mnu][3]){if(isNaN(_m[_mnu][3]))_los=getOffsetValue(_m[_mnu][3]); -else{_los=_m[_mnu][3]; -MouseX=0}}if(ns6&&!ns60){_los-=_sL; -_tos-=_sT}spos(_gm,MouseY+_tos,MouseX+_los)}else{for(_a=0; -_a<_d.images.length; -_a++){if(_d.images[_a].name==_arg[1])_po=_d.images[_a]}spos(_gm,_po.y+_po.height+getOffsetValue(_m[_mnu][2]),_po.x+getOffsetValue(_m[_mnu][3]))}}menuDisplay(_mnu,1); -_m[_mnu][21]=-1} -} - -function Opopup(_mn,_mp) -{clearTimeout(_MT); -closeAllMenus(); -if(_mn){_mnu=getMenuByName(_mn); -_sm[_sm.length]=_mnu; -menuDisplay(_mnu,1); -_m[_mnu][21]=-1} -} - -function popdown() -{_MT=setTimeout("closeAllMenus()",_menuCloseDelay) -} - -function _popi(_i) -{_cel=_i; -clearTimeout(_MT); -clearTimeout(_cMT); -clearTimeout(_oMT); -if(_mi[_i][34]=="disabled")return; -clearELs(_i); -if(_oldel>-1)clearELs(_oldel); -_mnu=-1; -_el=_i; -_itemRef=_i; -_mopen=_mi[_i][3]; -horiz=0; -if(_m[_mi[_i][0]][9])horiz=1; -itemOn(_i); -if(!_sm.length){_sm[_sm.length]=_mi[_i][0]; -_masterMenu=_mi[_i][0]}_iP=getParentMenuByItem(_el); -if(_iP==-1)_masterMenu=_mi[_i][0]; -set_status(_el); -_cMT=setTimeout("cm()",_menuOpenDelay); -if(_mopen&&(!_mi[_el][39]||inopenmode)){_gel=gmobj("el"+_i); -_gp=gpos(_gel); -_mnu=getMenuByName(_mopen); -if(_mi[_i][41])_m[_mnu][10]=1; -if(_mnu>-1){_gp=gpos(_gel); -_mnO=gmobj("menu"+_mnu); -_mp=gpos(_mnO); -if(horiz){_top=_gp[0]+_gp[2]+1; -_left=_gp[1]; -if(_m[_mnu][11]=="rtl"){_left=_left-(_mp[3]-_gp[3])-_mi[_i][27]}if(_m[_mi[_i][0]][5]=="bottom"){_top=(_gp[0]-_mp[2])}}else{_top=_gp[0]+_subOffsetTop; -_left=_gp[1]+_gp[3]+_subOffsetLeft; -if(_m[_mnu][11]=="rtl"){_left=_gp[1]-_mp[3]-_subOffsetLeft}}if(_left<0)_left=0; -if(_top<0)_top=0; -if(_m[_mnu][2]){if(isNaN(_m[_mnu][2])&&_m[_mnu][2].indexOf("offset=")==0){_os=_m[_mnu][2].substr(7,99); -_top=_top+parseInt(_os)}else{_top=_m[_mnu][2]}}if(_m[_mnu][3]){if(isNaN(_m[_mnu][3])&&_m[_mnu][3].indexOf("offset=")==0){_os=_m[_mnu][3].substr(7,99); -_left=_left+parseInt(_os)}else{_left=_m[_mnu][3]}}if(_left+_mp[3]>_bW+_sL){if(!horiz&&(_gp[1]-_mp[3])>0){_left=_gp[1]-_mp[3]-_subOffsetLeft}else{_left=(_bW-_mp[3])-2}}if(!horiz&&_top+_mp[2]>_bH+_sT){_top=(_bH-_mp[2])-2}if(!horiz){_top=_top-_m[_mnu][6][65]}else{_top--; -_left--}spos(_mnO,_top+_m[_mnu][6][65],_left+_m[_mnu][6][65]); -if(_m[_mnu][5])_setPosition(_mnu); -_zi++; -_mnb=gmobj("bord"+_mnu); -_oMT=setTimeout("menuDisplay("+_mnu+",1)",_menuOpenDelay); -if(_sm[_sm.length-1]!=_mnu)_sm[_sm.length]=_mnu}}_setPath(_iP) -} - -function _setPosition(_mnu) -{if(_m[_mnu][5]){_gm=gmobj("menu"+_mnu); -_gp=gpos(_gm); -_osl=0; -_omnu3=0; -if(isNaN(_m[_mnu][3])&&_m[_mnu][3].indexOf("offset=")==0){_omnu3=_m[_mnu][3]; -_m[_mnu][3]=_n; -_osl=_omnu3.substr(7,99); -_gm.leftOffset=_osl}_lft=_n; -if(!_m[_mnu][3]){if(_m[_mnu][5].indexOf("left")!=-1)_lft=0; -if(_m[_mnu][5].indexOf("center")!=-1)_lft=(_bW/2)-(_gp[3]/2); -if(_m[_mnu][5].indexOf("right")!=-1)_lft=_bW-_gp[3]; -if(_gm.leftOffset)_lft=_lft+parseInt(_gm.leftOffset)}_ost=0; -_omnu2=0; -if(isNaN(_m[_mnu][2])&&_m[_mnu][2].indexOf("offset=")==0){_omnu2=_m[_mnu][2]; -_m[_mnu][2]=_n; -_ost=_omnu2.substr(7,99); -_gm.topOffset=_ost}_tp=_n; -if(!_m[_mnu][2]>=0){_tp=_n; -if(_m[_mnu][5].indexOf("top")!=-1)_tp=0; -if(_m[_mnu][5].indexOf("middle")!=-1)_tp=(_bH/2)-(_gp[2]/2); -if(_m[_mnu][5].indexOf("bottom")!=-1)_tp=_bH-_gp[2]; -if(_gm.topOffset)_tp=_tp+parseInt(_gm.topOffset)}if(_lft<0)_lft=0; -spos(_gm,_tp,_lft); -if(_m[_mnu][19])_m[_mnu][19]=_tp; -if(_tp)_tp=_tp-_m[_mnu][6][65]; -if(_lft)_lft=_lft-_m[_mnu][6][65]; -_sb=gmobj("bord"+_mnu); -spos(_sb,_tp,_lft); -_gm._tp=_tp} -} - -function _MScan() -{_bW=self.innerWidth-16; -_bH=self.innerHeight-17; -_sT=self.pageYOffset; -if(_startM==1){for(_a=0; -_a<_m.length; -_a++){if(_m[_a][5]){_setPosition(_a)}}}else{if((_bH!=_oldbH)&&(_bW!=_oldbW)){location.reload()}}_startM=0; -_oldbH=_bH; -_oldbW=_bW}setInterval("_MScan()",200); - -function getMouseXY(e) -{ - MouseX=e.pageX; - MouseY=e.pageY -} - -_d.captureEvents(Event.MOUSEMOVE); -_d.onmousemove=getMouseXY;